feat: 添加登录功能
This commit is contained in:
parent
42b382398e
commit
681a83e8c0
|
|
@ -17,6 +17,7 @@
|
|||
"no-plusplus": 0,
|
||||
"prefer-destructuring": ["warn", { "object": true, "array": false }],
|
||||
"no-underscore-dangle": 0,
|
||||
"camelcase":0,
|
||||
// Start temporary rules
|
||||
// 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
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"radix": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-unused-expressions":0,
|
||||
"jsx-a11y/media-has-caption": 0,
|
||||
// Exchange
|
||||
"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 { ResetCSS } from '@pancakeswap/uikit'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import useEagerConnect from 'hooks/useEagerConnect'
|
||||
import { usePollCoreFarmData, useFetchProfile, usePollBlockNumber } from 'state/hooks'
|
||||
import { DatePickerPortal } from 'components/DatePicker'
|
||||
import { initAxios } from 'utils/request'
|
||||
import useToast from 'hooks/useToast'
|
||||
import GlobalStyle from './style/Global'
|
||||
import Menu from './components/Menu'
|
||||
import SuspenseWithChunkError from './components/SuspenseWithChunkError'
|
||||
|
|
@ -56,6 +59,12 @@ const App: React.FC = () => {
|
|||
useEagerConnect()
|
||||
useFetchProfile()
|
||||
usePollCoreFarmData()
|
||||
const dispatch = useDispatch()
|
||||
const toast = useToast()
|
||||
|
||||
useEffect(() => {
|
||||
initAxios(dispatch, toast)
|
||||
})
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
|
|
@ -76,7 +85,7 @@ const App: React.FC = () => {
|
|||
<Route path="/pools">
|
||||
<Pools />
|
||||
</Route>
|
||||
<Route path="/lottery">
|
||||
{/* <Route path="/lottery">
|
||||
<Lottery />
|
||||
</Route>
|
||||
<Route path="/ifo">
|
||||
|
|
@ -127,7 +136,7 @@ const App: React.FC = () => {
|
|||
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
|
||||
|
||||
{/* Redirect */}
|
||||
<Route path="/staking">
|
||||
{/* <Route path="/staking">
|
||||
<Redirect to="/pools" />
|
||||
</Route>
|
||||
<Route path="/syrup">
|
||||
|
|
@ -135,7 +144,7 @@ const App: React.FC = () => {
|
|||
</Route>
|
||||
<Route path="/nft">
|
||||
<Redirect to="/collectibles" />
|
||||
</Route>
|
||||
</Route> */}
|
||||
|
||||
{/* 404 */}
|
||||
<Route component={NotFound} />
|
||||
|
|
|
|||
|
|
@ -28,13 +28,6 @@ const Menu = (props) => {
|
|||
setLang={setLanguage}
|
||||
cakePriceUsd={cakePriceUsd.toNumber()}
|
||||
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}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 }>
|
||||
}
|
||||
}
|
||||
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,
|
||||
updateUserStakedBalance,
|
||||
} from './pools'
|
||||
export { setUserInfo, clearUserInfo } from './userInfo'
|
||||
export { profileFetchStart, profileFetchSucceeded, profileFetchFailed } from './profile'
|
||||
export { fetchStart, teamFetchSucceeded, fetchFailed, teamsFetchSucceeded } from './teams'
|
||||
export { setBlock } from './block'
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import blockReducer from './block'
|
|||
import collectiblesReducer from './collectibles'
|
||||
import votingReducer from './voting'
|
||||
import lotteryReducer from './lottery'
|
||||
import userInfo from './userInfo'
|
||||
|
||||
import application from './application/reducer'
|
||||
import { updateVersion } from './global/actions'
|
||||
|
|
@ -34,7 +35,7 @@ const store = configureStore({
|
|||
collectibles: collectiblesReducer,
|
||||
voting: votingReducer,
|
||||
lottery: lotteryReducer,
|
||||
|
||||
userInfo,
|
||||
// Exchange
|
||||
application,
|
||||
user,
|
||||
|
|
|
|||
|
|
@ -466,6 +466,28 @@ export interface UserRound {
|
|||
|
||||
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
|
||||
|
||||
export interface State {
|
||||
|
|
@ -478,5 +500,6 @@ export interface State {
|
|||
teams: TeamsState
|
||||
collectibles: CollectiblesState
|
||||
voting: VotingState
|
||||
userInfo: UserInfoState
|
||||
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