邀请页面
This commit is contained in:
@@ -116,6 +116,14 @@ export default {
|
||||
'draw.pwdPrompt': '请输入资金密码',
|
||||
'draw.success': '您的提币请求正在处理中!经过区块链网络确认后即可到账,请注意查收。',
|
||||
|
||||
'invite.title': '邀请新人',
|
||||
'invite.title2': '推荐新人领奖励',
|
||||
'invite.title3': '我的专属邀请码和链接',
|
||||
'invite.btn': '转发邀请到其他应用',
|
||||
'invite.step1': '分享好友',
|
||||
'invite.step2': '注册',
|
||||
'invite.step3': '领取奖金',
|
||||
|
||||
'profile.account': 'Account Settings',
|
||||
'profile.system': 'System Settings',
|
||||
'profile.identity': '实名认证',
|
||||
|
||||
@@ -116,6 +116,14 @@ export default {
|
||||
'draw.pwdPrompt': '请输入资金密码',
|
||||
'draw.success': '您的提币请求正在处理中!经过区块链网络确认后即可到账,请注意查收。',
|
||||
|
||||
'invite.title': '邀请新人',
|
||||
'invite.title2': '推荐新人领奖励',
|
||||
'invite.title3': '我的专属邀请码和链接',
|
||||
'invite.btn': '转发邀请到其他应用',
|
||||
'invite.step1': '分享好友',
|
||||
'invite.step2': '注册',
|
||||
'invite.step3': '领取奖金',
|
||||
|
||||
'profile.account': '账户管理',
|
||||
'profile.system': '系统设置',
|
||||
'profile.identity': '实名认证',
|
||||
|
||||
@@ -63,6 +63,9 @@ const model: Model = {
|
||||
coin,
|
||||
basic,
|
||||
balance: retrieveBalance(basic, coin),
|
||||
address: '',
|
||||
amount: '',
|
||||
password: '',
|
||||
});
|
||||
} catch (e) {
|
||||
yield put({
|
||||
@@ -140,7 +143,7 @@ const model: Model = {
|
||||
type: 'onUpdateState',
|
||||
confirmVisible: false,
|
||||
});
|
||||
Modal.info({
|
||||
Modal.success({
|
||||
title: formatMessage({ id: 'common.prompt' }),
|
||||
content: formatMessage({ id: 'draw.success' }),
|
||||
okText: formatMessage({ id: 'common.ok' }),
|
||||
|
||||
@@ -65,6 +65,7 @@ const CommunityPage: React.FC<HomeCommunityModelState & DispatchProp> = ({
|
||||
|
||||
<div
|
||||
className="right app-btn"
|
||||
onClick={() => dispatch(routerRedux.push('/invite'))}
|
||||
>
|
||||
{formatMessage({ id: 'home.community.invite' })}
|
||||
</div>
|
||||
|
||||
74
src/pages/invite/index.tsx
Normal file
74
src/pages/invite/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Title: 邀请新人
|
||||
* Routes:
|
||||
* - ./src/pages/routes
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { connect, DispatchProp } from 'dva';
|
||||
import { Icon, Button, message } from 'antd';
|
||||
import { formatMessage } from 'umi-plugin-locale';
|
||||
import { QRCanvas } from 'qrcanvas-react';
|
||||
import Clipboard from 'react-clipboard.js';
|
||||
import { InviteModelState } from './model';
|
||||
import Header from '@/components/Header';
|
||||
|
||||
import styles from './style.less';
|
||||
import { UserModelState } from '@/models/user';
|
||||
import { generateInviteHref } from './service';
|
||||
|
||||
const InvitePage: React.FC<DispatchProp & InviteModelState & UserModelState> = ({
|
||||
dispatch,
|
||||
user,
|
||||
}) => {
|
||||
if (!user) return null;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'invite/load' });
|
||||
}, []);
|
||||
const href = generateInviteHref(user.inviteCode);
|
||||
const onAddressCopied = () => {
|
||||
message.success(formatMessage({ id: 'common.copied' }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Header
|
||||
className="header"
|
||||
title={formatMessage({ id: 'invite.title' })}
|
||||
/>
|
||||
|
||||
<div className={styles.body}>
|
||||
<span className={styles.title2}>{formatMessage({ id: 'invite.title2' })}</span>
|
||||
<div className={styles.card}>
|
||||
<div className="rip" />
|
||||
<div className="content">
|
||||
<span className="title3">{formatMessage({ id: 'invite.title3' })}</span>
|
||||
<span className="invite-code">{user.inviteCode}</span>
|
||||
<QRCanvas options={{
|
||||
data: href,
|
||||
size: 200,
|
||||
}} />
|
||||
<div className="href-container">
|
||||
<span>{href}</span>
|
||||
<Clipboard
|
||||
component="div"
|
||||
className="address"
|
||||
data-clipboard-text={href}
|
||||
onSuccess={onAddressCopied}
|
||||
>
|
||||
<Icon type="copy" theme="filled" />
|
||||
</Clipboard>
|
||||
</div>
|
||||
<Button type="primary">
|
||||
{formatMessage({ id: 'invite.btn' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
({ invite, user }: any) => ({ ...invite, ...user }),
|
||||
)(InvitePage);
|
||||
28
src/pages/invite/model.ts
Normal file
28
src/pages/invite/model.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Model, routerRedux } from "dva";
|
||||
import _ from 'lodash';
|
||||
|
||||
export interface InviteModelState {
|
||||
}
|
||||
|
||||
const initState: InviteModelState = {
|
||||
};
|
||||
|
||||
const model: Model = {
|
||||
namespace: 'invite',
|
||||
|
||||
state: initState,
|
||||
|
||||
effects: {
|
||||
},
|
||||
|
||||
reducers: {
|
||||
onUpdateState(state, payload) {
|
||||
return {
|
||||
...state,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default model;
|
||||
18
src/pages/invite/service.ts
Normal file
18
src/pages/invite/service.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function generateInviteHref(invite: string): string {
|
||||
const postfix = invite ? `/check?inviteCode=${invite}` : '/check';
|
||||
if (isLocalhost) {
|
||||
return `${window.location.origin}${postfix}`
|
||||
}
|
||||
return `https://fivapp.github.io/#${postfix}`;
|
||||
}
|
||||
147
src/pages/invite/style.less
Normal file
147
src/pages/invite/style.less
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
@import '../../global.less';
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, @orange-one, @orange-two);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:global {
|
||||
.header {
|
||||
background-color: rgba(255,255,255,0);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ant-btn-primary {
|
||||
border-width: 0;
|
||||
background-color: @orange-one;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card {
|
||||
@height: 450px;
|
||||
@topHeight: @height * 0.4;
|
||||
@ripSize: 32px;
|
||||
@halfRip: @ripSize / 2;
|
||||
@bottomHeight: @height - @topHeight - @ripSize;
|
||||
@shadowAttr: 1px 1px 8px rgba(0, 0, 0, .2);
|
||||
|
||||
position: relative;
|
||||
margin: 20px 30px;
|
||||
padding-bottom: 20px;
|
||||
height: @height;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 20px 32px ;
|
||||
|
||||
&::before, &::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
box-shadow: @shadowAttr;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
height: @topHeight;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
height: @bottomHeight;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.rip {
|
||||
position: absolute;
|
||||
top: @topHeight;
|
||||
left: @halfRip;
|
||||
right: @halfRip;
|
||||
height: @ripSize;
|
||||
background-color: #fff;
|
||||
z-index: 2;
|
||||
|
||||
&::before, &::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: @ripSize / sqrt(2);
|
||||
height: @ripSize / sqrt(2);
|
||||
top: 50%;
|
||||
border: @halfRip solid transparent;
|
||||
border-radius: 100%;
|
||||
border-top-color: #fff;
|
||||
border-right-color: #fff;
|
||||
box-sizing: content-box;
|
||||
box-shadow: inset -2px 2px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: -(sqrt(2) / 4 + 1) * @ripSize;
|
||||
transform: translateY(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: -(sqrt(2) / 4 + 1) * @ripSize;
|
||||
transform: translateY(-50%) rotate(225deg);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.title3 {
|
||||
color: @orange-two;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.invite-code {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.href-container {
|
||||
margin: 20px 0;
|
||||
word-break: break-all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
font-size: 1.8rem;
|
||||
margin-left: 8px;
|
||||
color: @orange-one;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export interface User {
|
||||
phone: string;
|
||||
email?: string;
|
||||
token: string;
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export interface AreaCode {
|
||||
|
||||
Reference in New Issue
Block a user