@tradejs/app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +12 -0
  2. package/bin/tradejs-app.mjs +54 -0
  3. package/next-env.d.ts +6 -0
  4. package/next.config.mjs +31 -0
  5. package/package.json +60 -0
  6. package/src/app/actions/ai.ts +33 -0
  7. package/src/app/actions/backtest.ts +55 -0
  8. package/src/app/actions/kline.ts +18 -0
  9. package/src/app/actions/scanner.ts +10 -0
  10. package/src/app/actions/signal.ts +19 -0
  11. package/src/app/api/ai/route.ts +151 -0
  12. package/src/app/api/auth/[...nextauth]/route.ts +5 -0
  13. package/src/app/api/backtest/files/route.ts +60 -0
  14. package/src/app/api/backtest/order-log/[strategy]/[name]/route.ts +47 -0
  15. package/src/app/api/backtest/result/[strategy]/[name]/route.ts +63 -0
  16. package/src/app/api/backtest/test/[strategy]/[name]/route.ts +57 -0
  17. package/src/app/api/cron/route.ts +4 -0
  18. package/src/app/api/derivatives/[symbol]/[interval]/route.ts +57 -0
  19. package/src/app/api/derivatives/summary/route.ts +20 -0
  20. package/src/app/api/files/screenshot/[name]/route.ts +42 -0
  21. package/src/app/api/indicators/route.ts +24 -0
  22. package/src/app/api/kline/[provider]/[symbol]/[interval]/route.ts +123 -0
  23. package/src/app/api/scanner/[provider]/route.ts +41 -0
  24. package/src/app/api/scanner/route.ts +31 -0
  25. package/src/app/api/signal/[symbol]/[signalId]/route.ts +42 -0
  26. package/src/app/api/spread/[symbol]/[interval]/route.ts +57 -0
  27. package/src/app/api/spread/summary/route.ts +20 -0
  28. package/src/app/auth.ts +76 -0
  29. package/src/app/components/Backtest/CompareList/index.tsx +34 -0
  30. package/src/app/components/Backtest/TestCard/Chart/index.tsx +118 -0
  31. package/src/app/components/Backtest/TestCard/Chart/utils/index.ts +81 -0
  32. package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +21 -0
  33. package/src/app/components/Backtest/TestCard/ConfigDrawer/JsonCodeBlock.tsx +46 -0
  34. package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +94 -0
  35. package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +128 -0
  36. package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +18 -0
  37. package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +40 -0
  38. package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +24 -0
  39. package/src/app/components/Backtest/TestCard/Root/index.tsx +55 -0
  40. package/src/app/components/Backtest/TestCard/Skeleton/index.tsx +21 -0
  41. package/src/app/components/Backtest/TestCard/Stat/index.tsx +119 -0
  42. package/src/app/components/Backtest/TestCard/Title/index.tsx +84 -0
  43. package/src/app/components/Backtest/TestCard/context.ts +14 -0
  44. package/src/app/components/Backtest/TestCard/index.ts +28 -0
  45. package/src/app/components/Backtest/TestList/index.tsx +124 -0
  46. package/src/app/components/Dashboard/AiDrawer/Message.tsx +34 -0
  47. package/src/app/components/Dashboard/AiDrawer/index.tsx +163 -0
  48. package/src/app/components/Dashboard/KlineChart/figures/backtestFigureTypes.ts +7 -0
  49. package/src/app/components/Dashboard/KlineChart/figures/backtestMarkersPointFigure.ts +76 -0
  50. package/src/app/components/Dashboard/KlineChart/figures/circle.ts +15 -0
  51. package/src/app/components/Dashboard/KlineChart/figures/diamond.ts +25 -0
  52. package/src/app/components/Dashboard/KlineChart/figures/entryLinePointFigure.ts +1 -0
  53. package/src/app/components/Dashboard/KlineChart/figures/entryPointsPointFigure.ts +1 -0
  54. package/src/app/components/Dashboard/KlineChart/figures/entryZonePointFigure.ts +1 -0
  55. package/src/app/components/Dashboard/KlineChart/figures/index.ts +213 -0
  56. package/src/app/components/Dashboard/KlineChart/figures/label.ts +14 -0
  57. package/src/app/components/Dashboard/KlineChart/figures/rectangle.ts +20 -0
  58. package/src/app/components/Dashboard/KlineChart/figures/square.ts +21 -0
  59. package/src/app/components/Dashboard/KlineChart/figures/star.ts +39 -0
  60. package/src/app/components/Dashboard/KlineChart/figures/tradeZonePointFigure.ts +44 -0
  61. package/src/app/components/Dashboard/KlineChart/figures/trendLinePointFigure.ts +37 -0
  62. package/src/app/components/Dashboard/KlineChart/figures/trendLinePointsPointFigure.ts +26 -0
  63. package/src/app/components/Dashboard/KlineChart/figures/triangle.ts +23 -0
  64. package/src/app/components/Dashboard/KlineChart/hooks/index.ts +14 -0
  65. package/src/app/components/Dashboard/KlineChart/hooks/indicatorShared.ts +30 -0
  66. package/src/app/components/Dashboard/KlineChart/hooks/useAtrIndicator.ts +75 -0
  67. package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +533 -0
  68. package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +74 -0
  69. package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +155 -0
  70. package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +185 -0
  71. package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +62 -0
  72. package/src/app/components/Dashboard/KlineChart/hooks/useMaIndicator.ts +62 -0
  73. package/src/app/components/Dashboard/KlineChart/hooks/useManagedIndicator.ts +140 -0
  74. package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +212 -0
  75. package/src/app/components/Dashboard/KlineChart/hooks/useResize.ts +29 -0
  76. package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +122 -0
  77. package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +85 -0
  78. package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +243 -0
  79. package/src/app/components/Dashboard/KlineChart/hooks/useSupportResistanceLines.ts +125 -0
  80. package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +139 -0
  81. package/src/app/components/Dashboard/KlineChart/hooks/useVolIndicator.ts +18 -0
  82. package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +62 -0
  83. package/src/app/components/Dashboard/KlineChart/index.tsx +169 -0
  84. package/src/app/components/Dashboard/KlineChart/styles.ts +70 -0
  85. package/src/app/components/Dashboard/MainChart/index.tsx +35 -0
  86. package/src/app/components/Shared/AppShell.tsx +28 -0
  87. package/src/app/components/Shared/FavoriteButton/index.tsx +23 -0
  88. package/src/app/components/Shared/Filters/Backtest/index.tsx +164 -0
  89. package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +18 -0
  90. package/src/app/components/Shared/Filters/Indicators/index.tsx +21 -0
  91. package/src/app/components/Shared/Filters/Interval/index.tsx +31 -0
  92. package/src/app/components/Shared/Filters/Interval/intervals.ts +6 -0
  93. package/src/app/components/Shared/Filters/Provider/index.tsx +32 -0
  94. package/src/app/components/Shared/Filters/Root/index.tsx +28 -0
  95. package/src/app/components/Shared/Filters/Symbol/index.tsx +49 -0
  96. package/src/app/components/Shared/Filters/context.ts +17 -0
  97. package/src/app/components/Shared/Filters/index.ts +17 -0
  98. package/src/app/components/Shared/Sidebar/index.tsx +72 -0
  99. package/src/app/components/UI/ColorMode/index.tsx +112 -0
  100. package/src/app/components/UI/EmptyState/index.tsx +28 -0
  101. package/src/app/components/UI/OverlaySpinner/index.tsx +23 -0
  102. package/src/app/components/UI/Segment/index.tsx +23 -0
  103. package/src/app/components/UI/Select/index.tsx +81 -0
  104. package/src/app/components/UI/SelectWithSearch/index.tsx +104 -0
  105. package/src/app/components/UI/Switcher/index.tsx +24 -0
  106. package/src/app/components/UI/Toaster/index.tsx +45 -0
  107. package/src/app/components/UI/index.ts +8 -0
  108. package/src/app/favicon.ico +0 -0
  109. package/src/app/globals.css +5 -0
  110. package/src/app/layout.tsx +31 -0
  111. package/src/app/page.tsx +14 -0
  112. package/src/app/provider.tsx +39 -0
  113. package/src/app/routes/backtest/[test]/page.tsx +33 -0
  114. package/src/app/routes/backtest/page.tsx +374 -0
  115. package/src/app/routes/dashboard/[provider]/[symbol]/[interval]/page.tsx +124 -0
  116. package/src/app/routes/dashboard/page.tsx +20 -0
  117. package/src/app/routes/derivatives/page.tsx +202 -0
  118. package/src/app/routes/signin/page.tsx +155 -0
  119. package/src/app/store/data.ts +144 -0
  120. package/src/app/store/filters.ts +29 -0
  121. package/src/app/store/index.ts +13 -0
  122. package/src/app/store/indicators.ts +229 -0
  123. package/src/app/store/tests.ts +464 -0
  124. package/src/app/store/tickers.ts +89 -0
  125. package/src/proxy.ts +142 -0
  126. package/tsconfig.json +40 -0
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import _ from 'lodash';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ import { Select } from '@UI';
6
+ import { useFiltersContext } from '../context';
7
+
8
+ export const SelectBacktest = () => {
9
+ const { filters, backtestFiles, onChangeFilters } = useFiltersContext();
10
+ const STORAGE_KEY = 'backtest-strategy';
11
+
12
+ const tests = useMemo(
13
+ () => backtestFiles.filter((file) => file.value.startsWith(filters.symbol)),
14
+ [backtestFiles, filters.symbol],
15
+ );
16
+
17
+ const strategyItems = useMemo(() => {
18
+ const names = new Set<string>();
19
+ for (const test of tests) {
20
+ const strategyName = test.data?.strategyName;
21
+ if (typeof strategyName === 'string' && strategyName) {
22
+ names.add(strategyName);
23
+ }
24
+ }
25
+
26
+ return Array.from(names)
27
+ .sort()
28
+ .map((strategyName) => ({
29
+ label: strategyName,
30
+ value: strategyName,
31
+ }));
32
+ }, [tests]);
33
+
34
+ const [selectedStrategy, setSelectedStrategy] = useState<string>('');
35
+
36
+ useEffect(() => {
37
+ if (_.isEmpty(strategyItems)) {
38
+ setSelectedStrategy('');
39
+ return;
40
+ }
41
+
42
+ if (
43
+ filters.backtestStrategy &&
44
+ strategyItems.some((item) => item.value === filters.backtestStrategy) &&
45
+ selectedStrategy !== filters.backtestStrategy
46
+ ) {
47
+ setSelectedStrategy(filters.backtestStrategy);
48
+ return;
49
+ }
50
+
51
+ if (
52
+ selectedStrategy &&
53
+ strategyItems.some((item) => item.value === selectedStrategy)
54
+ ) {
55
+ return;
56
+ }
57
+
58
+ const saved =
59
+ typeof window !== 'undefined'
60
+ ? window.localStorage.getItem(STORAGE_KEY)
61
+ : null;
62
+
63
+ const defaultStrategy =
64
+ saved && strategyItems.some((item) => item.value === saved)
65
+ ? saved
66
+ : strategyItems[0]?.value || '';
67
+
68
+ if (defaultStrategy) {
69
+ setSelectedStrategy(defaultStrategy);
70
+ }
71
+ }, [strategyItems, filters.backtestStrategy, selectedStrategy]);
72
+
73
+ useEffect(() => {
74
+ if (!filters.backtestId) return;
75
+
76
+ const selectedTest = tests.find(
77
+ (test) => test.value === filters.backtestId,
78
+ );
79
+ const strategyName = selectedTest?.data?.strategyName;
80
+
81
+ if (
82
+ typeof strategyName === 'string' &&
83
+ strategyName &&
84
+ selectedStrategy !== strategyName
85
+ ) {
86
+ setSelectedStrategy(strategyName);
87
+ }
88
+ }, [filters.backtestId, tests, selectedStrategy]);
89
+
90
+ useEffect(() => {
91
+ if (selectedStrategy && typeof window !== 'undefined') {
92
+ window.localStorage.setItem(STORAGE_KEY, selectedStrategy);
93
+ }
94
+ }, [selectedStrategy]);
95
+
96
+ const onChange = (value: string[]) => {
97
+ onChangeFilters?.({
98
+ backtestId: value[0] || null,
99
+ });
100
+ };
101
+
102
+ const onChangeStrategy = (value: string[]) => {
103
+ const nextStrategy = value[0] || '';
104
+ setSelectedStrategy(nextStrategy);
105
+ if (nextStrategy && typeof window !== 'undefined') {
106
+ window.localStorage.setItem(STORAGE_KEY, nextStrategy);
107
+ }
108
+
109
+ const hasSelectedTest =
110
+ filters.backtestId &&
111
+ tests.some(
112
+ (test) =>
113
+ test.value === filters.backtestId &&
114
+ test.data?.strategyName === nextStrategy,
115
+ );
116
+
117
+ if (!hasSelectedTest) {
118
+ onChangeFilters?.({
119
+ backtestId: null,
120
+ backtestStrategy: nextStrategy || null,
121
+ });
122
+ return;
123
+ }
124
+
125
+ if (filters.backtestStrategy !== nextStrategy) {
126
+ onChangeFilters?.({ backtestStrategy: nextStrategy || null });
127
+ }
128
+ };
129
+
130
+ if (_.isEmpty(tests) || _.isEmpty(strategyItems) || !selectedStrategy) {
131
+ return null;
132
+ }
133
+
134
+ const strategyTests = tests.filter(
135
+ (test) => test.data?.strategyName === selectedStrategy,
136
+ );
137
+
138
+ return (
139
+ <>
140
+ <Select
141
+ placeholder="Strategy"
142
+ defaultValue={[selectedStrategy]}
143
+ value={[selectedStrategy]}
144
+ onChange={onChangeStrategy}
145
+ items={strategyItems}
146
+ width="220px"
147
+ />
148
+ <Select
149
+ placeholder="Backtest"
150
+ defaultValue={[filters.backtestId || '']}
151
+ value={[filters.backtestId || '']}
152
+ onChange={onChange}
153
+ items={[
154
+ {
155
+ label: 'Not selected',
156
+ value: '',
157
+ },
158
+ ...strategyTests,
159
+ ]}
160
+ width="240px"
161
+ />
162
+ </>
163
+ );
164
+ };
@@ -0,0 +1,18 @@
1
+ import { useFiltersContext } from '../context';
2
+ import { FavoriteButton } from '@shared/FavoriteButton';
3
+ import { useTickers } from '@store';
4
+
5
+ export const FavoriteIndicator = () => {
6
+ const {
7
+ filters: { symbol, provider },
8
+ } = useFiltersContext();
9
+ const { checkIsFavorite, toggleFavorite } = useTickers(provider || 'bybit');
10
+ const isFavorite = checkIsFavorite(symbol);
11
+
12
+ return (
13
+ <FavoriteButton
14
+ isFavorite={isFavorite}
15
+ onChangeFavorite={() => toggleFavorite(symbol)}
16
+ />
17
+ );
18
+ };
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import _ from 'lodash';
4
+ import { useIndicators } from '@store';
5
+ import { Select } from '@UI';
6
+
7
+ export const SelectIndicator = () => {
8
+ const { selectedIndicators, indicatorsItems, setEnabledIndicators } =
9
+ useIndicators();
10
+
11
+ return (
12
+ <Select
13
+ defaultValue={selectedIndicators}
14
+ onChange={setEnabledIndicators}
15
+ placeholder="Indicators"
16
+ items={indicatorsItems}
17
+ multiple={true}
18
+ width="240px"
19
+ />
20
+ );
21
+ };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { Segment } from '@UI';
4
+ import { Interval } from '@tradejs/types';
5
+ import { intervals } from './intervals';
6
+ import { useFiltersContext } from '../context';
7
+
8
+ export const SelectInterval = () => {
9
+ const { filters, onChangeFilters } = useFiltersContext();
10
+
11
+ const onChange = (value: string | null) => {
12
+ if (!value) {
13
+ return;
14
+ }
15
+
16
+ const newFilters = {
17
+ ...filters,
18
+ interval: value as Interval,
19
+ };
20
+
21
+ onChangeFilters?.(newFilters);
22
+ };
23
+
24
+ return (
25
+ <Segment
26
+ defaultValue={filters.interval}
27
+ onChange={onChange}
28
+ items={intervals}
29
+ />
30
+ );
31
+ };
@@ -0,0 +1,6 @@
1
+ export const intervals = [
2
+ { label: '15m', value: '15' },
3
+ { label: '1h', value: '60' },
4
+ { label: '4h', value: '240' },
5
+ { label: '12h', value: '720' },
6
+ ];
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { Select } from '@UI';
4
+ import { useFiltersContext } from '../context';
5
+
6
+ const items = [
7
+ { value: 'bybit', label: 'ByBit' },
8
+ { value: 'binance', label: 'Binance' },
9
+ { value: 'coinbase', label: 'Coinbase' },
10
+ ];
11
+
12
+ export const SelectProvider = () => {
13
+ const { filters, onChangeFilters } = useFiltersContext();
14
+
15
+ const onChange = (value: string[]) => {
16
+ if (!value[0]) return;
17
+ onChangeFilters?.({
18
+ provider: value[0] as 'bybit' | 'binance' | 'coinbase',
19
+ backtestId: null,
20
+ backtestStrategy: null,
21
+ });
22
+ };
23
+
24
+ return (
25
+ <Select
26
+ defaultValue={[filters.provider || 'bybit']}
27
+ onChange={onChange}
28
+ items={items}
29
+ width="140px"
30
+ />
31
+ );
32
+ };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { PropsWithChildren } from 'react';
4
+ import { FiltersContext } from '../context';
5
+ import { UIFilters, Items, OnChangeFilters } from '@tradejs/types';
6
+
7
+ interface RootProps {
8
+ tickers: Items;
9
+ backtestFiles: Items;
10
+ filters: UIFilters;
11
+ onChangeFilters?: OnChangeFilters;
12
+ }
13
+
14
+ export const Root = ({
15
+ filters,
16
+ tickers,
17
+ backtestFiles,
18
+ onChangeFilters,
19
+ children,
20
+ }: PropsWithChildren<RootProps>) => {
21
+ return (
22
+ <FiltersContext.Provider
23
+ value={{ filters, tickers, backtestFiles, onChangeFilters }}
24
+ >
25
+ {children}
26
+ </FiltersContext.Provider>
27
+ );
28
+ };
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ import _ from 'lodash';
4
+ import { SelectWithSearch } from '@UI';
5
+ import { SkeletonText, Stack, Show } from '@chakra-ui/react';
6
+ import { useFiltersContext } from '../context';
7
+
8
+ interface SelectSymbolProps {}
9
+
10
+ export const SelectSymbol = ({}: SelectSymbolProps) => {
11
+ const { filters, tickers, onChangeFilters } = useFiltersContext();
12
+ const loading = _.isEmpty(tickers);
13
+ const defaultInputValue =
14
+ tickers.find(({ value }) => value === filters.symbol)?.label ||
15
+ filters.symbol;
16
+
17
+ const onChange = (value: string[]) => {
18
+ if (_.isEmpty(value)) {
19
+ return;
20
+ }
21
+
22
+ const newFilters = {
23
+ symbol: value[0],
24
+ backtestId: null,
25
+ backtestStrategy: null,
26
+ };
27
+
28
+ onChangeFilters?.(newFilters);
29
+ };
30
+
31
+ return (
32
+ <Show
33
+ when={!loading}
34
+ fallback={
35
+ <Stack width="240px">
36
+ <SkeletonText height={8} noOfLines={1} />
37
+ </Stack>
38
+ }
39
+ >
40
+ <SelectWithSearch
41
+ defaultValue={[filters.symbol]}
42
+ defaultInputValue={defaultInputValue}
43
+ onChange={onChange}
44
+ items={tickers}
45
+ width="240px"
46
+ />
47
+ </Show>
48
+ );
49
+ };
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { UIFilters, Items, OnChangeFilters } from '@tradejs/types';
3
+
4
+ interface FiltersContextProps {
5
+ filters: UIFilters;
6
+ tickers: Items;
7
+ backtestFiles: Items;
8
+ onChangeFilters?: OnChangeFilters;
9
+ }
10
+
11
+ export const FiltersContext = createContext<FiltersContextProps>(
12
+ {} as FiltersContextProps,
13
+ );
14
+
15
+ export const useFiltersContext = () => {
16
+ return useContext(FiltersContext);
17
+ };
@@ -0,0 +1,17 @@
1
+ import { Root } from './Root';
2
+ import { SelectInterval } from './Interval';
3
+ import { SelectProvider } from './Provider';
4
+ import { SelectSymbol } from './Symbol';
5
+ import { SelectBacktest } from './Backtest';
6
+ import { SelectIndicator } from './Indicators';
7
+ import { FavoriteIndicator } from './FavoriteIndicator';
8
+
9
+ export const Filters = {
10
+ Root,
11
+ SelectProvider,
12
+ SelectInterval,
13
+ SelectSymbol,
14
+ SelectBacktest,
15
+ SelectIndicator,
16
+ FavoriteIndicator,
17
+ };
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import { Box, Flex, IconButton, VStack } from '@chakra-ui/react';
4
+ import { useRouter, usePathname } from 'next/navigation';
5
+ import { signOut } from 'next-auth/react';
6
+ import { FiActivity, FiBarChart2, FiLogOut, FiPlay } from 'react-icons/fi';
7
+
8
+ export const Sidebar = () => {
9
+ const router = useRouter();
10
+ const pathname = usePathname();
11
+
12
+ const navItems = [
13
+ {
14
+ icon: FiBarChart2,
15
+ label: 'Dashboard',
16
+ path: '/routes/dashboard',
17
+ },
18
+ {
19
+ icon: FiPlay,
20
+ label: 'Backtest',
21
+ path: '/routes/backtest',
22
+ },
23
+ {
24
+ icon: FiActivity,
25
+ label: 'Derivatives',
26
+ path: '/routes/derivatives',
27
+ },
28
+ ];
29
+
30
+ return (
31
+ <Box
32
+ position="fixed"
33
+ top={0}
34
+ left={0}
35
+ h="100vh"
36
+ w="60px"
37
+ bg="gray.800"
38
+ color="white"
39
+ py={4}
40
+ px={3}
41
+ zIndex={1000}
42
+ >
43
+ <Flex direction="column" h="100%" justify="space-between">
44
+ <VStack>
45
+ {navItems.map(({ icon: Icon, label, path }) => (
46
+ <IconButton
47
+ key={path}
48
+ mb={2}
49
+ aria-label={label}
50
+ size="md"
51
+ colorPalette="teal"
52
+ variant={pathname.includes(path) ? 'solid' : 'outline'}
53
+ onClick={() => router.push(path)}
54
+ >
55
+ <Icon />
56
+ </IconButton>
57
+ ))}
58
+ </VStack>
59
+
60
+ <IconButton
61
+ aria-label="Sign out"
62
+ size="md"
63
+ colorPalette="teal"
64
+ variant="outline"
65
+ onClick={() => signOut({ callbackUrl: '/routes/signin' })}
66
+ >
67
+ <FiLogOut />
68
+ </IconButton>
69
+ </Flex>
70
+ </Box>
71
+ );
72
+ };
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+
3
+ import {
4
+ ClientOnly,
5
+ IconButton,
6
+ Skeleton,
7
+ Span,
8
+ type IconButtonProps,
9
+ type SpanProps,
10
+ } from '@chakra-ui/react';
11
+ import { ThemeProvider, useTheme, type ThemeProviderProps } from 'next-themes';
12
+ import * as React from 'react';
13
+ import { LuMoon, LuSun } from 'react-icons/lu';
14
+
15
+ export interface ColorModeProviderProps extends ThemeProviderProps {}
16
+
17
+ export function ColorModeProvider(props: ColorModeProviderProps) {
18
+ return (
19
+ <ThemeProvider attribute="class" disableTransitionOnChange {...props} />
20
+ );
21
+ }
22
+
23
+ export type ColorMode = 'light' | 'dark';
24
+
25
+ export interface UseColorModeReturn {
26
+ colorMode: ColorMode;
27
+ setColorMode: (colorMode: ColorMode) => void;
28
+ toggleColorMode: () => void;
29
+ }
30
+
31
+ export function useColorMode(): UseColorModeReturn {
32
+ const { resolvedTheme, setTheme } = useTheme();
33
+ const toggleColorMode = () => {
34
+ setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
35
+ };
36
+ return {
37
+ colorMode: resolvedTheme as ColorMode,
38
+ setColorMode: setTheme,
39
+ toggleColorMode,
40
+ };
41
+ }
42
+
43
+ export function useColorModeValue<T>(light: T, dark: T) {
44
+ const { colorMode } = useColorMode();
45
+ return colorMode === 'dark' ? dark : light;
46
+ }
47
+
48
+ export function ColorModeIcon() {
49
+ const { colorMode } = useColorMode();
50
+ return colorMode === 'dark' ? <LuMoon /> : <LuSun />;
51
+ }
52
+
53
+ interface ColorModeButtonProps extends Omit<IconButtonProps, 'aria-label'> {}
54
+
55
+ export const ColorModeButton = React.forwardRef<
56
+ HTMLButtonElement,
57
+ ColorModeButtonProps
58
+ >(function ColorModeButton(props, ref) {
59
+ const { toggleColorMode } = useColorMode();
60
+ return (
61
+ <ClientOnly fallback={<Skeleton boxSize="8" />}>
62
+ <IconButton
63
+ onClick={toggleColorMode}
64
+ variant="ghost"
65
+ aria-label="Toggle color mode"
66
+ size="sm"
67
+ ref={ref}
68
+ {...props}
69
+ css={{
70
+ _icon: {
71
+ width: '5',
72
+ height: '5',
73
+ },
74
+ }}
75
+ >
76
+ <ColorModeIcon />
77
+ </IconButton>
78
+ </ClientOnly>
79
+ );
80
+ });
81
+
82
+ export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
83
+ function LightMode(props, ref) {
84
+ return (
85
+ <Span
86
+ color="fg"
87
+ display="contents"
88
+ className="chakra-theme light"
89
+ colorPalette="gray"
90
+ colorScheme="light"
91
+ ref={ref}
92
+ {...props}
93
+ />
94
+ );
95
+ },
96
+ );
97
+
98
+ export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
99
+ function DarkMode(props, ref) {
100
+ return (
101
+ <Span
102
+ color="fg"
103
+ display="contents"
104
+ className="chakra-theme dark"
105
+ colorPalette="gray"
106
+ colorScheme="dark"
107
+ ref={ref}
108
+ {...props}
109
+ />
110
+ );
111
+ },
112
+ );
@@ -0,0 +1,28 @@
1
+ import { EmptyState as EmptyStateUI, VStack } from '@chakra-ui/react';
2
+ import { IconType } from 'react-icons';
3
+
4
+ interface EmptyStateProps {
5
+ title: string;
6
+ description: React.ReactNode;
7
+ icon: IconType;
8
+ }
9
+
10
+ export const EmptyState = ({
11
+ icon: Icon,
12
+ title,
13
+ description,
14
+ }: EmptyStateProps) => {
15
+ return (
16
+ <EmptyStateUI.Root>
17
+ <EmptyStateUI.Content>
18
+ <EmptyStateUI.Indicator>
19
+ <Icon />
20
+ </EmptyStateUI.Indicator>
21
+ <VStack textAlign="center">
22
+ <EmptyStateUI.Title>{title}</EmptyStateUI.Title>
23
+ <EmptyStateUI.Description>{description}</EmptyStateUI.Description>
24
+ </VStack>
25
+ </EmptyStateUI.Content>
26
+ </EmptyStateUI.Root>
27
+ );
28
+ };
@@ -0,0 +1,23 @@
1
+ import { Box, Center, VStack, Spinner, Text } from '@chakra-ui/react';
2
+
3
+ export const OverlaySpinner = () => {
4
+ return (
5
+ <>
6
+ <Box
7
+ pos="absolute"
8
+ inset="0"
9
+ bg="gray.900"
10
+ opacity="0.5"
11
+ zIndex={'overlay'}
12
+ />
13
+ <Box pos="absolute" inset="0" zIndex={'modal'}>
14
+ <Center h="full">
15
+ <VStack colorPalette="teal">
16
+ <Spinner color="colorPalette.500" size="lg" />
17
+ <Text color="colorPalette.500">Loading...</Text>
18
+ </VStack>
19
+ </Center>
20
+ </Box>
21
+ </>
22
+ );
23
+ };
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { SegmentGroup } from '@chakra-ui/react';
4
+ import { Items } from '@tradejs/types';
5
+
6
+ interface SegmentProps {
7
+ defaultValue: string;
8
+ items: Items;
9
+ onChange?: (value: string | null) => void;
10
+ }
11
+
12
+ export const Segment = ({ defaultValue, items, onChange }: SegmentProps) => {
13
+ return (
14
+ <SegmentGroup.Root
15
+ size="md"
16
+ defaultValue={defaultValue}
17
+ onValueChange={(e) => onChange?.(e.value)}
18
+ >
19
+ <SegmentGroup.Indicator />
20
+ <SegmentGroup.Items items={items} />
21
+ </SegmentGroup.Root>
22
+ );
23
+ };