diff --git a/src/layouts/Sidebar.tsx b/src/layouts/Sidebar.tsx
index 2c20fb2c4..fd28b7272 100644
--- a/src/layouts/Sidebar.tsx
+++ b/src/layouts/Sidebar.tsx
@@ -2,6 +2,7 @@ import { ReactComponent as BuyCryptoIcon } from 'assets/icons/buy_crypto.svg';
import { ReactComponent as CoHavestIcon } from 'assets/icons/co_harvest.svg';
import { ReactComponent as GovernanceIcon } from 'assets/icons/governance.svg';
import { ReactComponent as GpuStakingIcon } from 'assets/icons/gpu_staking.svg';
+import { ReactComponent as GpuCreditIcon } from 'assets/icons/gpu_credit.svg';
import { ReactComponent as HomeBaseIcon } from 'assets/icons/homebase.svg';
import { ReactComponent as GitIcon } from 'assets/icons/ic_github.svg';
import { ReactComponent as DiscordIcon } from 'assets/icons/ic_discord.svg';
@@ -103,6 +104,7 @@ const Sidebar: React.FC<{}> = React.memo((props) => {
{renderLink('/homebase', 'Homebase', setLink,
)}
{renderLink('/gpu-staking', 'GPU Staking', setLink,
)}
+ {renderLink('/gpu-credit', 'GPU Credit', setLink,
)}
{renderLink(
'https://scan.orai.io/validators',
'ORAI Staking',
diff --git a/src/pages/GithubLogin/index.module.scss b/src/pages/GithubLogin/index.module.scss
new file mode 100644
index 000000000..336bdb0c3
--- /dev/null
+++ b/src/pages/GithubLogin/index.module.scss
@@ -0,0 +1,54 @@
+.wrapper {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ margin-top: 100px;
+}
+
+.redirect {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+
+ .desc {
+ text-align: center;
+ font-size: 30px;
+ }
+}
+
+.loader-wrap {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ padding: 6px 0;
+
+ .loader {
+ width: 30px;
+ aspect-ratio: 2;
+ --_g: no-repeat radial-gradient(circle closest-side, #a19d9d 90%, #6b696900);
+ background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%;
+ background-size: calc(100% / 3) 50%;
+ animation: l3 1s infinite linear;
+ }
+ @keyframes l3 {
+ 20% {
+ background-position: 0% 0%, 50% 50%, 100% 50%;
+ }
+ 40% {
+ background-position: 0% 100%, 50% 0%, 100% 50%;
+ }
+ 60% {
+ background-position: 0% 50%, 50% 100%, 100% 0%;
+ }
+ 80% {
+ background-position: 0% 50%, 50% 50%, 100% 100%;
+ }
+ }
+}
diff --git a/src/pages/GithubLogin/index.tsx b/src/pages/GithubLogin/index.tsx
new file mode 100644
index 000000000..e12a5eed9
--- /dev/null
+++ b/src/pages/GithubLogin/index.tsx
@@ -0,0 +1,64 @@
+import React, { useEffect } from 'react';
+import classNames from 'classnames/bind';
+import { useDispatch } from 'react-redux';
+import { toast } from 'react-toastify';
+import { useLocation } from 'react-router-dom';
+
+import axios from 'rest/request';
+import { setAccountName, setCredit, setTokens } from 'reducer/auth';
+import { getLatestCsrf } from 'utils/githubCode';
+import styles from './index.module.scss';
+
+const cx = classNames.bind(styles);
+
+const GithubLogin: React.FC = () => {
+ const dispatch = useDispatch();
+ const { search } = useLocation();
+
+ const urlParams = new URLSearchParams(search);
+ const code = urlParams.get('code');
+ const latestCsrf = urlParams.get('state');
+
+ useEffect(() => {
+ const login = async () => {
+ const resp = await axios.post(`${process.env.REACT_APP_BASE_GPU_API_URL}/github-auth`, { code });
+
+ const { accessToken, refreshToken, creditRemain, username } = resp.data;
+
+ dispatch(setTokens({ access: accessToken, refresh: refreshToken }));
+ dispatch(setCredit(creditRemain));
+ dispatch(setAccountName(username));
+ };
+
+ if (code && latestCsrf && latestCsrf === getLatestCsrf()) {
+ // TODO: refactor this for better UX
+ login().then(() => {
+ toast.success('Login success');
+ setTimeout(() => {
+ window.history.back();
+ }, 3);
+ }).catch(() => {
+ toast.error('Login failed');
+ setTimeout(() => {
+ window.location.assign('/');
+ }, 5);
+ });
+ } else {
+ // TODO: print error message
+ console.log('not login');
+ }
+ }, [code, latestCsrf]);
+
+ return (
+
+
+
Logging in by Github
+
+
+
+ );
+};
+
+export default GithubLogin;
diff --git a/src/pages/GpuCredit/index.module.scss b/src/pages/GpuCredit/index.module.scss
new file mode 100644
index 000000000..ebb3c7b24
--- /dev/null
+++ b/src/pages/GpuCredit/index.module.scss
@@ -0,0 +1,282 @@
+@import 'src/styles/mixins';
+@import 'src/styles/themes';
+
+.container {
+ display: inline-flex;
+ // padding: 32px 0px;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 56px;
+
+ .title {
+ color: var(--Colors-Neutral-Text-heading, #f7f7f7);
+ font-size: 24px;
+ font-weight: bold;
+ line-height: 28px;
+ }
+
+ a.external {
+ color: var(--Colors-Primary-Text-action, #b999f3);
+ font-family: 'IBM Plex Sans';
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 20px; /* 125% */
+
+ @include mobile {
+ display: none;
+ }
+
+ svg {
+ path {
+ fill: #b999f3;
+ }
+
+ vertical-align: sub;
+ width: 20px;
+ height: 20px;
+ }
+ }
+
+ .statistics {
+ display: flex;
+ gap: var(--Dimensions-40, 40px);
+
+ align-self: stretch;
+ align-items: stretch;
+
+ & > * {
+ flex: 1;
+
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .daily-credit-usage {
+ .chart {
+ padding-top: 20px;
+
+ canvas {
+ max-height: 250px;
+ }
+ }
+ }
+
+ .gpu-statistics {
+ .content {
+ flex-grow: 1;
+
+ .basic-info {
+ // width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+
+ & > * {
+ flex: 1;
+ }
+
+ .header {
+ color: var(--Colors-Neutral-Text-placeholder, #83838a);
+ }
+ .value {
+ font-size: 1.5em;
+ }
+ }
+
+ .gpu-detail {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+
+ & > * {
+ flex: 1;
+ }
+
+ .gpu {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+
+ .usage {
+ display: flex;
+ justify-content: space-between;
+
+ .text {
+ color: var(--Colors-Neutral-Text-placeholder, #83838a);
+ }
+ }
+ .progress {
+ height: var(--Dimensions-16, 16px);
+ border: var(--Colors-Neutral-Border-default, #383b40) 1px solid;
+
+ .percent-value {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: var(--Colors-Neutral-Border-progress-bar, #83838a) 1px solid;
+ background-color: var(--Colors-Neutral-Surface-progess-bar, rgba(131, 131, 138, 0.5));
+ }
+ }
+ }
+ .link {
+ line-height: 32px;
+ }
+ }
+ }
+ }
+ }
+
+ .personal-info {
+ padding: 32px 24px 32px 24px;
+ border-radius: var(--Dimension-Corner-Radius-row, 8px);
+ background: linear-gradient(180deg, #1f1f20 0%, #141416 321px);
+
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+
+ .intro {
+ display: flex;
+ gap: 32px;
+
+ & > .text {
+ width: 60%;
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+
+ .content {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+
+ p {
+ color: var(--Colors-Neutral-Text-placeholder, #83838a);
+ }
+
+ .link {
+ line-height: 32px;
+ }
+ }
+ }
+ .interaction {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .credit-balance {
+ background-image: url('../../assets/images/bg-gpu-credit-balance.png');
+ background-repeat: no-repeat;
+ background-size: cover;
+ border-radius: 8px;
+ border: 1px solid var(--Colors-Neutral-Border-default, #383b40);
+ flex-grow: 1;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 24px;
+
+ .info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .text {
+ font-size: 16px;
+ line-height: 24px;
+ }
+ .value {
+ font-size: 32px;
+ line-height: 130%;
+ color: var(--Colors-Neutral-Text-heading, #f7f7f7);
+ }
+ }
+ .list-buttons {
+ display: flex;
+ gap: 16px;
+
+ button {
+ padding: 14px 24px;
+
+ &:disabled {
+ opacity: 1;
+ color: var(--Colors-Neutral-Text-disable, #47474b);
+
+ display: flex;
+ justify-content: center;
+
+ svg {
+ path {
+ fill: var(--Colors-Neutral-Text-disable, #47474b);
+ }
+
+ width: 20px;
+ height: 20px;
+ }
+ }
+ }
+ }
+ }
+
+ .go-to-panel {
+ display: flex;
+ justify-content: space-between;
+ gap: 16px;
+
+ a {
+ flex: 1;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 12px;
+ cursor: pointer;
+ border-radius: var(--Dimension-Corner-Radius-check-box, 4px);
+ border: 1px solid var(--Colors-Neutral-Border-light, #242627);
+ // width: 45%;
+ }
+ }
+ }
+ }
+ .credit-usage-history {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ .title {
+ line-height: 24px;
+ }
+ .table {
+ border-radius: var(--Dimension-Corner-Radius-row, 8px);
+ border: 1px solid var(--Colors-Neutral-Border-default, #383b40);
+ padding-bottom: 24px;
+
+ th {
+ color: var(--Colors-Neutral-Text-placeholder, #83838a);
+ font-family: 'IBM Plex Sans';
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 150%; /* 18px */
+ letter-spacing: 0.012px;
+ }
+
+ .no-data {
+ height: 222px;
+ border-bottom: none;
+
+ td {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/pages/GpuCredit/index.tsx b/src/pages/GpuCredit/index.tsx
new file mode 100644
index 000000000..edaa43f50
--- /dev/null
+++ b/src/pages/GpuCredit/index.tsx
@@ -0,0 +1,362 @@
+import { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { toast } from 'react-toastify';
+import cn from 'classnames/bind';
+import { Bar } from 'react-chartjs-2';
+import { CategoryScale, BarElement } from 'chart.js';
+import Chart from 'chart.js/auto';
+
+import Content from 'layouts/Content';
+import { ReactComponent as JumpIcon } from 'assets/icons/jump.svg';
+import { ReactComponent as GitHubIcon } from 'assets/icons/github.svg';
+import { ReactComponent as NoCreditUsageHistoryIcon } from 'assets/images/no-credit-usage-history.svg';
+import { ReactComponent as AddIcon } from 'assets/icons/Add.svg';
+import { ReactComponent as TimeIcon } from 'assets/icons/time.svg';
+import { ReactComponent as KeyIcon } from 'assets/icons/key.svg';
+import JupyterHubImg from 'assets/images/jupyterhub.png';
+import { Table, TableHeaderProps } from 'components/Table';
+import { Button } from 'components/Button';
+import { handleConnectGithub } from 'components/GithubConnect/helper';
+import { RootState } from 'store/configure';
+import axios, { axiosAuth } from 'rest/request';
+import styles from './index.module.scss';
+
+const cx = cn.bind(styles);
+Chart.register(CategoryScale, BarElement);
+const baseApiUrl = process.env.REACT_APP_BASE_GPU_API_URL;
+
+const GpuCredit: React.FC<{}> = () => {
+ const [gpuStatistics, setGpuStatistics] = useState({
+ totalCards: 12,
+ totalVRAM: 295,
+ totalUsage: 0
+ });
+ const [gpuStatus, setGpuStatus] = useState([]);
+ const [creditUsageHistoryData, setCreditUsageHistoryData] = useState([]);
+ const [dailyCreditUsage, setDailyCreditUsage] = useState([]);
+
+ const tokens = useSelector((state: RootState) => state.auth.token);
+ const credit = useSelector((state: RootState) => state.auth.credit);
+ const loggedIn = !!tokens.access;
+
+ const [chartData, setChartData] = useState({
+ labels: dailyCreditUsage.map((data) => new Date(data.date).getDate()),
+ datasets: [
+ {
+ // label: undefined,
+ data: dailyCreditUsage.map((data) => data.credit),
+ backgroundColor: ['#7332E7'],
+ borderColor: '#B999F3',
+ borderWidth: 2,
+ borderRadius: 4,
+ borderSkipped: false,
+ barThickness: 20
+ }
+ ]
+ });
+
+ useEffect(() => {
+ if (loggedIn) {
+ const getData = async () => {
+ let promises;
+
+ try {
+ promises = await Promise.all([
+ axiosAuth.get(`${baseApiUrl}/credit-usage-history?filter=negative`, {
+ headers: { Authorization: `Bearer ${tokens.access}` }
+ }),
+ axiosAuth.get(`${baseApiUrl}/credit-usage-per-day`, {
+ headers: { Authorization: `Bearer ${tokens.access}` }
+ })
+ ]);
+ } catch (e) {
+ console.error('Credit usage history:', e);
+
+ toast.error('Failed to qery credit usage');
+ }
+
+ // TODO: refactor this, promises may get [undefined, undefined] due to interceptors
+ if (promises && promises[0] && promises[1]) {
+ const creditUsageHistory = promises[0].data.creditUsageHistory;
+ const totalCreditUsageEachDay = promises[1].data.totalCreditUsageEachDay;
+
+ setCreditUsageHistoryData(creditUsageHistory);
+ setDailyCreditUsage(totalCreditUsageEachDay);
+ }
+ };
+ getData();
+ } else {
+ axios
+ .get(`${baseApiUrl}/credit-usage-per-day-of-server`)
+ .then(({ data }) => {
+ setDailyCreditUsage(data.totalCreditUsageEachDayByServer);
+ })
+ .catch((e) => {
+ console.error('Credit usage per day:', e);
+
+ // TODO: handle error
+ toast.error('Failed to fetch daily credit usage');
+ });
+ }
+
+ axios
+ .get(`${baseApiUrl}/gpu-statistics`)
+ .then(({ data }) => {
+ const gpuStatisticsData = {
+ totalCards: data.numberOfCards,
+ totalVRAM: Math.round(data.totalVRAM),
+ totalUsage: data.totalUsage.toFixed(2).replace(/\.0+$/, '')
+ };
+ setGpuStatistics(gpuStatisticsData);
+
+ // TODO: set interval
+ // TODO: set real data
+ const gpuStatusData = data.hosts
+ .sort((host1, host2) => host2.vramUsage - host1.vramUsage)
+ .slice(0, 2)
+ .map((host) => ({
+ name: `${host.name} ${host.gpuNumber}x ${host.gpuName}`,
+ capacity: Math.floor(host.totalVRAM),
+ currentUsage: Math.floor(host.vramUsage)
+ }));
+ setGpuStatus(gpuStatusData);
+ })
+ .catch((e) => {
+ console.error('gpu-statistics:', e);
+
+ // TODO: handle error
+ toast.error('Failed to fetch gpu statistics');
+ });
+ }, [tokens]);
+
+ useEffect(() => {
+ setChartData({
+ labels: dailyCreditUsage.map((data) => new Date(data.date).getDate()),
+ datasets: [
+ {
+ // label: undefined,
+ data: dailyCreditUsage.map((data) => data.credit),
+ backgroundColor: ['#7332E7'],
+ borderColor: '#B999F3',
+ borderWidth: 2,
+ borderRadius: 4,
+ borderSkipped: false,
+ barThickness: 20
+ }
+ ]
+ });
+ }, [dailyCreditUsage]);
+
+ const gpuStatusElements = gpuStatus.map((detail, idx) => {
+ const percentUsage = Math.floor((detail.currentUsage / detail.capacity) * 100);
+ return (
+
+
+
{detail.name} Usage:
+
+ {Math.floor(detail.currentUsage)}/{detail.capacity} GB
+
+
+
+
+ );
+ });
+
+ const creditUsageHistoryHeaders: TableHeaderProps
= {
+ timestamp: {
+ name: 'TIMESTAMP',
+ accessor: (data) => new Date(data.timestamp).toISOString().replace(/T.+$/, ''),
+ width: '30%',
+ align: 'left',
+ padding: `0px 0px 0px 24px`
+ },
+ action: {
+ name: 'ACTION',
+ accessor: (data) => data.action,
+ width: '40%',
+ align: 'left'
+ },
+ creditUsage: {
+ name: 'CREDIT USAGE',
+ accessor: (data) => data.credit,
+ width: '30%',
+ align: 'left'
+ }
+ };
+
+ return (
+
+
+
+
+
Total Credit Usage Each Day
+
+
+
+
+
+
GPU Statistics
+
+
+
+
Total GPU Cards:
+
{gpuStatistics.totalCards}
+
+
+
Total VRAM:
+
{gpuStatistics.totalVRAM} GB
+
+
+
Total Usage:
+
{gpuStatistics.totalUsage}%
+
+
+
+
+
+
+
+
+
+
+
Decentralized GPU Infra
+
+
+ Decentralized GPU infra is crucial for the advancement of AI x Blockchain. DApps builders that are
+ eager to scale AI in Blockchain environment can apply for GPU Credits offered by Oraichain Labs and
+ expand their project capabilities.
+
+ GPU Credits Offering supercharges AI businesses and empowers them to thrive on blockchain environment.
+ Decentralized GPU infrastructure is crucial for the advancement of AI x Blockchain.
+
+ With Decentralized GPU Infra, projects can:
+ - Optimize cost
+
- Reduce common infrastructure risks of relying on a single provider.
+
+
+ Learn more {}
+
+
+
+
+
+
+
Your Credits
+
{tokens.access ? credit : 0}
+
+
+ {loggedIn ? (
+ <>
+ }>
+ Buy Credit
+
+
+ >
+ ) : (
+ }>
+ Login to apply
+
+ )}
+
+
+
+
+
+
+
Credit Usage History
+
+
+
+
+ No records found
+ |
+
+ }
+ />
+
+
+
+
+
+ );
+};
+
+export default GpuCredit;
diff --git a/src/reducer/auth.ts b/src/reducer/auth.ts
new file mode 100644
index 000000000..5baddaabb
--- /dev/null
+++ b/src/reducer/auth.ts
@@ -0,0 +1,40 @@
+import type { PayloadAction } from '@reduxjs/toolkit';
+import { createSlice } from '@reduxjs/toolkit';
+import { AuthState } from './type';
+
+const initialState: AuthState = {
+ token: {
+ access: '',
+ refresh: ''
+ },
+ accountName: '',
+ credit: 0
+};
+
+const authSlice = createSlice({
+ name: 'auth',
+ initialState,
+ reducers: {
+ setTokens: (state, action: PayloadAction) => {
+ state.token = action.payload;
+ },
+ setAccountName: (state, action: PayloadAction) => {
+ state.accountName = action.payload;
+ },
+ setCredit: (state, action: PayloadAction) => {
+ state.credit = action.payload;
+ },
+ reset: (state) => {
+ state.token = {
+ access: '',
+ refresh: ''
+ };
+ state.accountName = '';
+ state.credit = 0;
+ }
+ }
+});
+
+export const { setTokens, setAccountName, setCredit, reset } = authSlice.actions;
+
+export default authSlice.reducer;
diff --git a/src/reducer/type.ts b/src/reducer/type.ts
index 8d9f13ecc..7166f064f 100644
--- a/src/reducer/type.ts
+++ b/src/reducer/type.ts
@@ -196,6 +196,15 @@ export interface OrderResponseContract {
orders: OrderDetailFromContract[];
}
+export interface AuthState {
+ token: {
+ access: string;
+ refresh: string;
+ };
+ accountName: string;
+ credit: number;
+}
+
export enum OrderStatus {
OPEN = 'OPEN',
FUL_FILLED = 'FUL_FILLED',
diff --git a/src/rest/request.ts b/src/rest/request.ts
index 10708ca06..ccb5140d7 100644
--- a/src/rest/request.ts
+++ b/src/rest/request.ts
@@ -1,8 +1,10 @@
import Axios from 'axios';
+import { store } from 'store/configure';
+import { setTokens, reset } from 'reducer/auth';
import { throttleAdapterEnhancer, retryAdapterEnhancer } from 'axios-extensions';
import { AXIOS_TIMEOUT, AXIOS_THROTTLE_THRESHOLD } from '@oraichain/oraidex-common';
-const axios = Axios.create({
+export default Axios.create({
timeout: AXIOS_TIMEOUT,
retryTimes: 3,
// cache will be enabled by default in 2 seconds
@@ -14,4 +16,48 @@ const axios = Axios.create({
baseURL: process.env.REACT_APP_BASE_API_URL
});
-export default axios;
+export const axiosAuth = Axios.create({
+ timeout: AXIOS_TIMEOUT,
+ adapter: retryAdapterEnhancer(
+ throttleAdapterEnhancer(Axios.defaults.adapter!, {
+ threshold: AXIOS_THROTTLE_THRESHOLD
+ })
+ ),
+ baseURL: process.env.REACT_APP_BASE_GPU_API_URL
+});
+
+let refreshTokenPromise;
+
+const getRefreshToken = () => {
+ return Axios.post(`${process.env.REACT_APP_BASE_GPU_API_URL}/refresh-token`, {
+ refreshToken: store.getState().auth.token.refresh
+ });
+};
+
+axiosAuth.interceptors.response.use(
+ (res) => res,
+ (error) => {
+ if (error.response?.status === 401) {
+ if (!refreshTokenPromise) {
+ // check for an existing in-progress request
+ // if nothing is in-progress, start a new refresh token request
+ refreshTokenPromise = getRefreshToken().then((resp) => {
+ refreshTokenPromise = null; // clear state
+ return resp.data; // resolve with the new token
+ });
+ }
+ return refreshTokenPromise
+ .then((tokens) => {
+ const newTokens = {
+ access: tokens.accessToken,
+ refresh: tokens.refreshToken
+ };
+ store.dispatch(setTokens(newTokens));
+ })
+ .catch(() => {
+ store.dispatch(reset());
+ });
+ }
+ return Promise.reject(error);
+ }
+);
diff --git a/src/routes.tsx b/src/routes.tsx
index 1dae340b4..a6cc6d435 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -3,9 +3,10 @@ import Loader from 'components/Loader';
import NotFound from 'pages/NotFound';
import { Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
-
+import GpuCredit from 'pages/GpuCredit';
import GpuStaking from 'pages/GpuStaking';
import UniversalSwap from 'pages/UniversalSwap/index';
+import GithubLogin from 'pages/GithubLogin';
export default () => (
(
} />
} />
} />
+ } />
+ } />
} />
diff --git a/src/store/configure.ts b/src/store/configure.ts
index 824ea36b8..963f2fdb7 100644
--- a/src/store/configure.ts
+++ b/src/store/configure.ts
@@ -6,6 +6,7 @@ import tradingReducer from '../reducer/tradingSlice';
import walletReducer from '../reducer/wallet';
import poolChartReducer from '../reducer/poolChartSlice';
import AddressBookReducer from '../reducer/addressBook';
+import AuthReducer from '../reducer/auth';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import { PERSIST_CONFIG_KEY } from './constants';
@@ -22,7 +23,8 @@ const rootReducer = combineReducers({
trading: tradingReducer,
wallet: walletReducer,
poolChart: poolChartReducer,
- addressBook: AddressBookReducer
+ addressBook: AddressBookReducer,
+ auth: AuthReducer
});
const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
diff --git a/src/utils/githubCode.ts b/src/utils/githubCode.ts
new file mode 100644
index 000000000..4aaae771d
--- /dev/null
+++ b/src/utils/githubCode.ts
@@ -0,0 +1,13 @@
+const LATEST_CSRF_TOKEN = 'latest_csrf_token';
+
+export const setLatestCsrf = (csrf: string) => {
+ // if (localStorage.getItem(LATEST_CSRF_TOKEN)) {
+ // return;
+ // }
+ // console.log('SET NEW CSRF', csrf);
+ localStorage.setItem(LATEST_CSRF_TOKEN, csrf);
+};
+
+export const getLatestCsrf = () => {
+ return localStorage.getItem(LATEST_CSRF_TOKEN);
+};
diff --git a/tsconfig.json b/tsconfig.json
index dc13a8ee9..47843fcab 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -30,4 +30,4 @@
"exclude": [
"node_modules",
]
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index 317dd8837..c9aca70cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2757,6 +2757,11 @@
dependencies:
hash-sum "^2.0.0"
+"@kurkle/color@^0.3.0":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
+ integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
+
"@leapwallet/cosmos-snap-provider@0.1.25":
version "0.1.25"
resolved "https://registry.yarnpkg.com/@leapwallet/cosmos-snap-provider/-/cosmos-snap-provider-0.1.25.tgz#f256cd4c7ef89aa9209ed8dbaf16487db24bde10"
@@ -3730,7 +3735,7 @@
sha3 "^2.1.4"
shx "^0.3.4"
-"@tippyjs/react@^4.2.0":
+"@tippyjs/react@^4.2.6":
version "4.2.6"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
@@ -6169,6 +6174,13 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
+chart.js@^4.4.4:
+ version "4.4.4"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.4.tgz#b682d2e7249f7a0cbb1b1d31c840266ae9db64b7"
+ integrity sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==
+ dependencies:
+ "@kurkle/color" "^0.3.0"
+
check-types@^11.2.3:
version "11.2.3"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71"
@@ -7384,6 +7396,11 @@ dotenv@^16.3.1:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.1.tgz#1d9931f1d3e5d2959350d1250efab299561f7f11"
integrity sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==
+dotenv@^16.4.5:
+ version "16.4.5"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
+ integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
@@ -13792,6 +13809,11 @@ react-app-rewired@^2.2.1:
dependencies:
semver "^5.6.0"
+react-chartjs-2@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d"
+ integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
+
react-dev-utils@^12.0.1:
version "12.0.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"