feat: 添加登录功能
This commit is contained in:
parent
42b382398e
commit
681a83e8c0
|
|
@ -17,6 +17,7 @@
|
||||||
"no-plusplus": 0,
|
"no-plusplus": 0,
|
||||||
"prefer-destructuring": ["warn", { "object": true, "array": false }],
|
"prefer-destructuring": ["warn", { "object": true, "array": false }],
|
||||||
"no-underscore-dangle": 0,
|
"no-underscore-dangle": 0,
|
||||||
|
"camelcase":0,
|
||||||
// Start temporary rules
|
// Start temporary rules
|
||||||
// These rules are here just to keep the lint error to 0 during the migration to the new rule set
|
// These rules are here just to keep the lint error to 0 during the migration to the new rule set
|
||||||
// They need to be removed and fixed as soon as possible
|
// They need to be removed and fixed as soon as possible
|
||||||
|
|
@ -26,6 +27,7 @@
|
||||||
"@typescript-eslint/no-explicit-any": 0,
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
"radix": 0,
|
"radix": 0,
|
||||||
"import/no-extraneous-dependencies": 0,
|
"import/no-extraneous-dependencies": 0,
|
||||||
|
"no-unused-expressions":0,
|
||||||
"jsx-a11y/media-has-caption": 0,
|
"jsx-a11y/media-has-caption": 0,
|
||||||
// Exchange
|
// Exchange
|
||||||
"no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state", "memo"] }],
|
"no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state", "memo"] }],
|
||||||
|
|
|
||||||
17
src/App.tsx
17
src/App.tsx
|
|
@ -1,10 +1,13 @@
|
||||||
import React, { lazy } from 'react'
|
import React, { lazy, useEffect } from 'react'
|
||||||
import { Router, Redirect, Route, Switch } from 'react-router-dom'
|
import { Router, Redirect, Route, Switch } from 'react-router-dom'
|
||||||
import { ResetCSS } from '@pancakeswap/uikit'
|
import { ResetCSS } from '@pancakeswap/uikit'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import useEagerConnect from 'hooks/useEagerConnect'
|
import useEagerConnect from 'hooks/useEagerConnect'
|
||||||
import { usePollCoreFarmData, useFetchProfile, usePollBlockNumber } from 'state/hooks'
|
import { usePollCoreFarmData, useFetchProfile, usePollBlockNumber } from 'state/hooks'
|
||||||
import { DatePickerPortal } from 'components/DatePicker'
|
import { DatePickerPortal } from 'components/DatePicker'
|
||||||
|
import { initAxios } from 'utils/request'
|
||||||
|
import useToast from 'hooks/useToast'
|
||||||
import GlobalStyle from './style/Global'
|
import GlobalStyle from './style/Global'
|
||||||
import Menu from './components/Menu'
|
import Menu from './components/Menu'
|
||||||
import SuspenseWithChunkError from './components/SuspenseWithChunkError'
|
import SuspenseWithChunkError from './components/SuspenseWithChunkError'
|
||||||
|
|
@ -56,6 +59,12 @@ const App: React.FC = () => {
|
||||||
useEagerConnect()
|
useEagerConnect()
|
||||||
useFetchProfile()
|
useFetchProfile()
|
||||||
usePollCoreFarmData()
|
usePollCoreFarmData()
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initAxios(dispatch, toast)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
|
|
@ -76,7 +85,7 @@ const App: React.FC = () => {
|
||||||
<Route path="/pools">
|
<Route path="/pools">
|
||||||
<Pools />
|
<Pools />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/lottery">
|
{/* <Route path="/lottery">
|
||||||
<Lottery />
|
<Lottery />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/ifo">
|
<Route path="/ifo">
|
||||||
|
|
@ -127,7 +136,7 @@ const App: React.FC = () => {
|
||||||
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||||
|
|
||||||
{/* Redirect */}
|
{/* Redirect */}
|
||||||
<Route path="/staking">
|
{/* <Route path="/staking">
|
||||||
<Redirect to="/pools" />
|
<Redirect to="/pools" />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/syrup">
|
<Route path="/syrup">
|
||||||
|
|
@ -135,7 +144,7 @@ const App: React.FC = () => {
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/nft">
|
<Route path="/nft">
|
||||||
<Redirect to="/collectibles" />
|
<Redirect to="/collectibles" />
|
||||||
</Route>
|
</Route> */}
|
||||||
|
|
||||||
{/* 404 */}
|
{/* 404 */}
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,6 @@ const Menu = (props) => {
|
||||||
setLang={setLanguage}
|
setLang={setLanguage}
|
||||||
cakePriceUsd={cakePriceUsd.toNumber()}
|
cakePriceUsd={cakePriceUsd.toNumber()}
|
||||||
links={config(t)}
|
links={config(t)}
|
||||||
profile={{
|
|
||||||
username: profile?.username,
|
|
||||||
image: profile?.nft ? `/images/nfts/${profile.nft?.images.sm}` : undefined,
|
|
||||||
profileLink: '/profile',
|
|
||||||
noProfileLink: '/profile',
|
|
||||||
showPip: !profile?.username,
|
|
||||||
}}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const CACHE_TOKEN = 'token'
|
||||||
|
export const CACHE_USERIFNO = 'userInfo'
|
||||||
|
export const CACHE_ACCOUNT = 'account'
|
||||||
|
export const CACHE_INVITE_CODE = 'inviteCode'
|
||||||
|
|
@ -9,3 +9,4 @@ interface Window {
|
||||||
bnbSign?: (address: string, message: string) => Promise<{ publicKey: string; signature: string }>
|
bnbSign?: (address: string, message: string) => Promise<{ publicKey: string; signature: string }>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
declare module 'axios'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import request from 'utils/request'
|
||||||
|
|
||||||
|
export const login = (params) => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/login',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const queryLoginHash = (params) => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/login/hash',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import request from 'utils/request'
|
||||||
|
|
||||||
|
export const queryUserInfo = () => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/user',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const queryUserAvatarList = () => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/avatar/user/list',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const queryUserInviteList = () => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/user/invite/top/list',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUserAvater = (params) => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/user/update/avatar',
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const updateUserInfo = (data) => {
|
||||||
|
return request.request({
|
||||||
|
url: '/v1/user/update',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ export {
|
||||||
updateUserPendingReward,
|
updateUserPendingReward,
|
||||||
updateUserStakedBalance,
|
updateUserStakedBalance,
|
||||||
} from './pools'
|
} from './pools'
|
||||||
|
export { setUserInfo, clearUserInfo } from './userInfo'
|
||||||
export { profileFetchStart, profileFetchSucceeded, profileFetchFailed } from './profile'
|
export { profileFetchStart, profileFetchSucceeded, profileFetchFailed } from './profile'
|
||||||
export { fetchStart, teamFetchSucceeded, fetchFailed, teamsFetchSucceeded } from './teams'
|
export { fetchStart, teamFetchSucceeded, fetchFailed, teamsFetchSucceeded } from './teams'
|
||||||
export { setBlock } from './block'
|
export { setBlock } from './block'
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import blockReducer from './block'
|
||||||
import collectiblesReducer from './collectibles'
|
import collectiblesReducer from './collectibles'
|
||||||
import votingReducer from './voting'
|
import votingReducer from './voting'
|
||||||
import lotteryReducer from './lottery'
|
import lotteryReducer from './lottery'
|
||||||
|
import userInfo from './userInfo'
|
||||||
|
|
||||||
import application from './application/reducer'
|
import application from './application/reducer'
|
||||||
import { updateVersion } from './global/actions'
|
import { updateVersion } from './global/actions'
|
||||||
|
|
@ -34,7 +35,7 @@ const store = configureStore({
|
||||||
collectibles: collectiblesReducer,
|
collectibles: collectiblesReducer,
|
||||||
voting: votingReducer,
|
voting: votingReducer,
|
||||||
lottery: lotteryReducer,
|
lottery: lotteryReducer,
|
||||||
|
userInfo,
|
||||||
// Exchange
|
// Exchange
|
||||||
application,
|
application,
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,28 @@ export interface UserRound {
|
||||||
|
|
||||||
export type UserTicketsResponse = [ethers.BigNumber[], number[], boolean[]]
|
export type UserTicketsResponse = [ethers.BigNumber[], number[], boolean[]]
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id?: number
|
||||||
|
avatar?: string
|
||||||
|
name?: string
|
||||||
|
address?: string
|
||||||
|
description?: string
|
||||||
|
invite_num?: string
|
||||||
|
invite_link?: string
|
||||||
|
email?: string
|
||||||
|
twitter?: string
|
||||||
|
facebook?: string
|
||||||
|
invite_code?: string
|
||||||
|
scoring_account?: number
|
||||||
|
invite_reward?: number
|
||||||
|
inviter_address?: string
|
||||||
|
}
|
||||||
|
export interface UserInfoState {
|
||||||
|
userInfo: UserInfo
|
||||||
|
token?: string
|
||||||
|
account?: string
|
||||||
|
}
|
||||||
|
|
||||||
// Global state
|
// Global state
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
|
|
@ -478,5 +500,6 @@ export interface State {
|
||||||
teams: TeamsState
|
teams: TeamsState
|
||||||
collectibles: CollectiblesState
|
collectibles: CollectiblesState
|
||||||
voting: VotingState
|
voting: VotingState
|
||||||
|
userInfo: UserInfoState
|
||||||
lottery: LotteryState
|
lottery: LotteryState
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { queryLoginHash, login as userLogin } from 'services/login'
|
||||||
|
import useWeb3Provider from 'hooks/useActiveWeb3React'
|
||||||
|
import { signMessage } from 'utils/web3React'
|
||||||
|
import { setUserInfo } from 'state/actions'
|
||||||
|
import { CACHE_INVITE_CODE } from 'config/constants/cacheKey'
|
||||||
|
import useToast from 'hooks/useToast'
|
||||||
|
import { State } from '../types'
|
||||||
|
|
||||||
|
export const useAccount = (ellipsis?: boolean) => {
|
||||||
|
const account = useSelector((state: State) => state.userInfo.account)
|
||||||
|
if (ellipsis) {
|
||||||
|
return account ? `${account.substring(0, 4)}...${account.substring(account.length - 4)}` : null
|
||||||
|
}
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
// 钱包登录 但是未签名获取到token
|
||||||
|
export const useUnactiveAccount = () => {
|
||||||
|
const { account } = useWeb3React()
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useToken = () => {
|
||||||
|
return useSelector((state: State) => state.userInfo.token)
|
||||||
|
}
|
||||||
|
export const useUserInfo = () => {
|
||||||
|
return useSelector((state: State) => state.userInfo.userInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserPortrait = () => {
|
||||||
|
const userInfo = useSelector((state: State) => state.userInfo.userInfo)
|
||||||
|
return userInfo?.avatar || '/images/common/portrait.png'
|
||||||
|
}
|
||||||
|
// 签名登录
|
||||||
|
export const useSignLogin = () => {
|
||||||
|
const unActiveAccount = useUnactiveAccount()
|
||||||
|
const { library } = useWeb3Provider()
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { toastError } = useToast()
|
||||||
|
const signLogin = async () => {
|
||||||
|
try {
|
||||||
|
const data = await queryLoginHash({ address: unActiveAccount })
|
||||||
|
const signHash = await signMessage(library, unActiveAccount, data.hash)
|
||||||
|
const inviteCode = sessionStorage.getItem(CACHE_INVITE_CODE)
|
||||||
|
const userInfo = await userLogin({
|
||||||
|
hash: data.hash,
|
||||||
|
signature: signHash,
|
||||||
|
invite_code: inviteCode,
|
||||||
|
})
|
||||||
|
dispatch(setUserInfo({ ...userInfo.user_info, token: userInfo.token }))
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
// e.message&&toastError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return signLogin
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||||
|
import { queryUserInfo } from 'services/user'
|
||||||
|
import { CACHE_TOKEN, CACHE_ACCOUNT, CACHE_USERIFNO } from 'config/constants/cacheKey'
|
||||||
|
import multicall from 'utils/multicall'
|
||||||
|
// import inviteAbi from 'config/abi/invite.json'
|
||||||
|
// import { getInviteAddress } from 'utils/addressHelpers'
|
||||||
|
import { getBalanceNumber } from 'utils/formatBalance'
|
||||||
|
import { UserInfoState, UserInfo } from '../types'
|
||||||
|
|
||||||
|
const initialState: UserInfoState = {
|
||||||
|
userInfo: {},
|
||||||
|
token: localStorage.getItem(CACHE_TOKEN),
|
||||||
|
account: localStorage.getItem(CACHE_ACCOUNT),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchUserInfo = createAsyncThunk<UserInfo, null>('userInfo/fetchUserInfo', async () => {
|
||||||
|
const result = await queryUserInfo()
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// export const fetchUserInviteInfo = createAsyncThunk<number, string>('userInfo/fetchUserInviteInfo', async (account) => {
|
||||||
|
// const [canWithdrawReward] = await multicall(inviteAbi, [
|
||||||
|
// {
|
||||||
|
// address: getInviteAddress(),
|
||||||
|
// name: 'canGetRewardMap',
|
||||||
|
// params: [account],
|
||||||
|
// },
|
||||||
|
// ])
|
||||||
|
// return getBalanceNumber(canWithdrawReward)
|
||||||
|
// })
|
||||||
|
|
||||||
|
export const userInfoSlice = createSlice({
|
||||||
|
name: 'userInfo',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setUserInfo: (state, action) => {
|
||||||
|
const info = action.payload
|
||||||
|
state.userInfo = info
|
||||||
|
state.account = info.address
|
||||||
|
info.token && (state.token = info.token)
|
||||||
|
localStorage.setItem(CACHE_TOKEN, state.token)
|
||||||
|
localStorage.setItem(CACHE_USERIFNO, JSON.stringify(info))
|
||||||
|
localStorage.setItem(CACHE_ACCOUNT, info.address)
|
||||||
|
},
|
||||||
|
clearUserInfo: (state) => {
|
||||||
|
state.userInfo = {}
|
||||||
|
state.account = ''
|
||||||
|
state.token = ''
|
||||||
|
localStorage.removeItem(CACHE_TOKEN)
|
||||||
|
localStorage.removeItem(CACHE_USERIFNO)
|
||||||
|
localStorage.removeItem(CACHE_ACCOUNT)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchUserInfo.fulfilled, (state, action) => {
|
||||||
|
state.userInfo = action.payload
|
||||||
|
localStorage.setItem(CACHE_USERIFNO, JSON.stringify(state.userInfo))
|
||||||
|
})
|
||||||
|
// .addCase(fetchUserInviteInfo.fulfilled, (state, action) => {
|
||||||
|
// state.userInfo = { ...state.userInfo, invite_reward: action.payload }
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
export const { setUserInfo, clearUserInfo } = userInfoSlice.actions
|
||||||
|
export default userInfoSlice.reducer
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface ProtraitDetailType {
|
||||||
|
id?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InviteDetailType {
|
||||||
|
id: number
|
||||||
|
created_at: string
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
invite_num: number
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Dispatch } from 'react'
|
||||||
|
import axios from 'axios'
|
||||||
|
// import { clearUserInfo } from 'state/actions'
|
||||||
|
|
||||||
|
// create an axios instance
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: process.env.REACT_APP_REQUEST_URL,
|
||||||
|
timeout: 10000, // request timeout
|
||||||
|
})
|
||||||
|
let hasInit = false
|
||||||
|
export const initAxios = (dispatch: Dispatch<any>, toast) => {
|
||||||
|
if (hasInit) return
|
||||||
|
hasInit = true
|
||||||
|
// request interceptor
|
||||||
|
request.interceptors.request.use(
|
||||||
|
(memo: any) => {
|
||||||
|
// do something before request is sent
|
||||||
|
|
||||||
|
memo.headers.token = localStorage.getItem('token')
|
||||||
|
return memo
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// response interceptor
|
||||||
|
request.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
const res: any = response.data
|
||||||
|
if (res.code !== 200) {
|
||||||
|
if (res.code === 401) {
|
||||||
|
// dispatch(clearUserInfo())
|
||||||
|
toast.toastError('Login expiration')
|
||||||
|
return Promise.reject(new Error('Login expiration'))
|
||||||
|
}
|
||||||
|
toast.toastError(res.msg)
|
||||||
|
return Promise.reject(res.msg || 'Error')
|
||||||
|
}
|
||||||
|
return res.data
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
||||||
Loading…
Reference in New Issue