create dapp

This commit is contained in:
Kamen Stoykov 2019-05-08 19:05:54 +03:00
parent fb58dee434
commit fe4f2dd055
18 changed files with 737 additions and 12 deletions

View File

@ -26,6 +26,8 @@
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"rc-slider": "8.6.9",
"rc-tooltip": "3.7.3",
"web3-utils": "^1.0.0-beta.35"
},
"scripts": {

View File

@ -0,0 +1,57 @@
<svg class="lds-spin" width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><g transform="translate(80,50)">
<g transform="rotate(0)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="1">
<animateTransform attributeName="transform" type="scale" begin="-0.875s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.875s"></animate>
</circle>
</g>
</g><g transform="translate(71.21320343559643,71.21320343559643)">
<g transform="rotate(45)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.875">
<animateTransform attributeName="transform" type="scale" begin="-0.75s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.75s"></animate>
</circle>
</g>
</g><g transform="translate(50,80)">
<g transform="rotate(90)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.75">
<animateTransform attributeName="transform" type="scale" begin="-0.625s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.625s"></animate>
</circle>
</g>
</g><g transform="translate(28.786796564403577,71.21320343559643)">
<g transform="rotate(135)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.625">
<animateTransform attributeName="transform" type="scale" begin="-0.5s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.5s"></animate>
</circle>
</g>
</g><g transform="translate(20,50.00000000000001)">
<g transform="rotate(180)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.5">
<animateTransform attributeName="transform" type="scale" begin="-0.375s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.375s"></animate>
</circle>
</g>
</g><g transform="translate(28.78679656440357,28.786796564403577)">
<g transform="rotate(225)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.375">
<animateTransform attributeName="transform" type="scale" begin="-0.25s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.25s"></animate>
</circle>
</g>
</g><g transform="translate(49.99999999999999,20)">
<g transform="rotate(270)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.25">
<animateTransform attributeName="transform" type="scale" begin="-0.125s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="-0.125s"></animate>
</circle>
</g>
</g><g transform="translate(71.21320343559643,28.78679656440357)">
<g transform="rotate(315)">
<circle cx="0" cy="0" r="10" fill="#4360df" fill-opacity="0.125">
<animateTransform attributeName="transform" type="scale" begin="0s" values="1.1 1.1;1 1" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animateTransform>
<animate attributeName="fill-opacity" keyTimes="0;1" dur="1s" repeatCount="indefinite" values="1;0" begin="0s"></animate>
</circle>
</g>
</g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import RCSlider from 'rc-slider'
import './Slider.scss'
const Slider = props => {
const { min, max, value, onChange } = props
return (
<div className="slider">
<RCSlider min={min} max={max} value={value} onChange={onChange} />
</div>
)
}
Slider.propTypes = {
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
}
export default Slider

View File

@ -0,0 +1,28 @@
@import '../../../common/styles/variables';
.slider {
margin-top: -3px;
}
.slider .rc-slider-rail {
height: 8px;
}
.slider .rc-slider-track {
height: 8px;
background-color: $link-color;
}
.slider .rc-slider-handle {
width: 28px;
height: 28px;
margin-top: -9px;
margin-left: -13px;
border: 0;
background: $link-color;
}
.slider .rc-slider-handle:hover,
.slider .rc-slider-handle:active {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.48);
}

View File

@ -0,0 +1,3 @@
import Slider from './Slider'
export default Slider

View File

@ -5,6 +5,11 @@ const submit = {
url: '',
img: '',
category: '',
imgControl: false,
imgControlZoom: 0,
imgControlMove: false,
imgControlX: 0,
imgControlY: 0,
}
export default submit

View File

@ -0,0 +1,8 @@
const transactionStatus = {
dappName: 'slow.Trade',
dappUrl: '/images/dapps/slowtrade.png',
progress: false,
published: true,
}
export default transactionStatus

View File

@ -5,6 +5,7 @@ import dapps from '../../modules/Dapps/Dapps.reducer'
import vote from '../../modules/Vote/Vote.reducer'
import submit from '../../modules/Submit/Submit.reducer'
import desktopMenu from '../../modules/DesktopMenu/DesktopMenu.reducer'
import transactionStatus from '../../modules/TransactionStatus/TransactionStatus.recuder'
export default history =>
combineReducers({
@ -14,4 +15,5 @@ export default history =>
vote,
submit,
desktopMenu,
transactionStatus,
})

View File

@ -8,6 +8,7 @@ import Dapps from '../Dapps'
import Vote from '../Vote'
import Submit from '../Submit'
import Terms from '../Terms/Terms'
import TransactionStatus from '../TransactionStatus'
class Router extends React.Component {
componentDidMount() {
@ -27,6 +28,7 @@ class Router extends React.Component {
</Switch>,
<Vote key={2} />,
<Submit key={3} />,
<TransactionStatus key={4} />,
]
}
}

View File

@ -7,6 +7,12 @@ import {
onInputNameAction,
onInputUrlAction,
onInputDescAction,
onImgReadAction,
onImgZoomAction,
onImgMoveControlAction,
onImgMoveAction,
onImgDoneAction,
onImgCancelAction,
} from './Submit.reducer'
const mapStateToProps = state => state.submit
@ -15,6 +21,12 @@ const mapDispatchToProps = dispatch => ({
onInputName: name => dispatch(onInputNameAction(name)),
onInputDesc: name => dispatch(onInputDescAction(name)),
onInputUrl: name => dispatch(onInputUrlAction(name)),
onImgRead: imgBase64 => dispatch(onImgReadAction(imgBase64)),
onImgZoom: zoom => dispatch(onImgZoomAction(zoom)),
onImgMoveControl: move => dispatch(onImgMoveControlAction(move)),
onImgMove: (x, y) => dispatch(onImgMoveAction(x, y)),
onImgCancel: () => dispatch(onImgCancelAction()),
onImgDone: imgBase64 => dispatch(onImgDoneAction(imgBase64)),
})
export default withRouter(

View File

@ -3,17 +3,59 @@ import PropTypes from 'prop-types'
import styles from './Submit.module.scss'
import Modal from '../../common/components/Modal'
import CategorySelector from '../CategorySelector/CategorySelector.picker'
import Slider from '../../common/components/Slider/Slider'
import 'rc-slider/assets/index.css'
import 'rc-tooltip/assets/bootstrap.css'
class Submit extends React.Component {
constructor(props) {
super(props)
this.imgCanvas = React.createRef()
this.previousMoveX = 0
this.previousMoveY = 0
this.onInputName = this.onInputName.bind(this)
this.onInputDesc = this.onInputDesc.bind(this)
this.onInputUrl = this.onInputUrl.bind(this)
this.onChangeImage = this.onChangeImage.bind(this)
this.onChangeZoom = this.onChangeZoom.bind(this)
this.onStartMove = this.onStartMove.bind(this)
this.onMouseMove = this.onMouseMove.bind(this)
this.onTouchMove = this.onTouchMove.bind(this)
this.onEndMove = this.onEndMove.bind(this)
this.onImgDone = this.onImgDone.bind(this)
}
onSelectCategory(category) {
this.setState({ category: category })
componentDidUpdate() {
const { img, imgControlZoom, imgControlX, imgControlY } = this.props
if (img === '') return
const canvas = this.imgCanvas.current
if (canvas === null) return
const ctx = canvas.getContext('2d')
const imgNode = new Image()
imgNode.onload = () => {
const s = parseInt(
Math.min(imgNode.width, imgNode.height) /
((imgControlZoom + 100) / 100),
10,
)
const k = canvas.width / s
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(
imgNode,
0,
0,
imgNode.width,
imgNode.height,
imgControlX + (s - imgNode.width) * 0.5 * k,
imgControlY + (s - imgNode.height) * 0.5 * k,
imgNode.width * k,
imgNode.height * k,
)
}
imgNode.src = img
}
onInputName(e) {
@ -31,18 +73,98 @@ class Submit extends React.Component {
onInputUrl(e.target.value)
}
onChangeImage(e) {
const input = e.target
const { files } = e.target
if (files === 0) return
const file = files[0]
const fileReader = new FileReader()
fileReader.onload = ev => {
const { onImgRead } = this.props
input.value = ''
onImgRead(ev.target.result)
}
fileReader.readAsDataURL(file, 'UTF-8')
}
onChangeZoom(value) {
const { onImgZoom } = this.props
onImgZoom(value)
}
onStartMove() {
const { onImgMoveControl } = this.props
this.previousMoveX = -1
this.previousMoveY = -1
onImgMoveControl(true)
}
onMouseMove(e) {
const { imgControlMove, imgControlX, imgControlY, onImgMove } = this.props
if (!imgControlMove) return
const x = imgControlX + e.movementX
const y = imgControlY + e.movementY
requestAnimationFrame(() => {
onImgMove(x, y)
})
}
onTouchMove(e) {
const { imgControlMove, imgControlX, imgControlY, onImgMove } = this.props
if (!imgControlMove) return
const touch = e.touches[0]
if (this.previousMoveX !== -1 && this.previousMoveY !== -1) {
const x = imgControlX - (this.previousMoveX - touch.screenX)
const y = imgControlY - (this.previousMoveY - touch.screenY)
requestAnimationFrame(() => {
onImgMove(x, y)
})
}
this.previousMoveX = touch.screenX
this.previousMoveY = touch.screenY
}
onEndMove() {
const { onImgMoveControl } = this.props
onImgMoveControl(false)
}
onImgDone() {
const { onImgDone } = this.props
const canvas = this.imgCanvas.current
const imgBase64 = canvas.toDataURL('image/jpg')
onImgDone(imgBase64)
}
render() {
const { visible, onClickClose, name, desc, url, img, category } = this.props
const {
visible,
onClickClose,
name,
desc,
url,
img,
category,
imgControl,
imgControlZoom,
onImgCancel,
} = this.props
return (
<Modal
visible={visible && window.location.hash === '#submit'}
onClickClose={onClickClose}
windowClassName={styles.modalWindow}
contentClassName={imgControl ? styles.modalContentImgControl : ''}
>
<div className={styles.title}>Submit a Ðapp</div>
<div className={styles.cnt}>
<div>
<div className={styles.title}>
{imgControl ? 'Position and size your image' : 'Submit a Ðapp'}
</div>
<div className={imgControl ? styles.cntWithImgControl : ''}>
<div className={imgControl ? styles.withImgControl : ''}>
<div className={styles.block}>
<div className={styles.labelRow}>
<span>Name of your Ðapp</span>
@ -85,7 +207,13 @@ class Submit extends React.Component {
<span>Choose image</span>
<div
className={styles.imgHolder}
style={{ backgroundImage: img }}
style={{ backgroundImage: `url(${img})` }}
/>
<input
className={styles.uploader}
type="file"
onChange={this.onChangeImage}
accept=".jpg, .png"
/>
</div>
<div className={styles.imgInfo}>
@ -106,8 +234,8 @@ class Submit extends React.Component {
</div>
<div className={`${styles.block} ${styles.blockSubmit}`}>
<div className={styles.terms}>
By continuing you agree to our{' '}
<a href="#">Terms and Conditions.</a>{' '}
By continuing you agree to our
<a href="#">Terms and Conditions.</a>
</div>
<button
className={styles.submitButton}
@ -118,7 +246,54 @@ class Submit extends React.Component {
</button>
</div>
</div>
<div className={styles.imgResizer} />
{imgControl && (
<div className={styles.imgControl}>
<div
className={styles.imgCanvasCnt}
onMouseDown={this.onStartMove}
onMouseMove={this.onMouseMove}
onMouseUp={this.onEndMove}
onMouseLeave={this.onEndMove}
onTouchStart={this.onStartMove}
onTouchMove={this.onTouchMove}
onTouchEnd={this.onEndMove}
onTouchCancel={this.onEndMove}
>
<canvas
ref={this.imgCanvas}
className={styles.imgCanvas}
width="160"
height="160"
/>
</div>
<div className={styles.controls}>
<div className={styles.slider}>
<div className={styles.minZoom} />
<Slider
min={0}
max={300}
value={imgControlZoom}
onChange={this.onChangeZoom}
/>
<div className={styles.maxZoom} />
</div>
<div className={styles.actionsCnt}>
<button
className={`${styles.button} ${styles.cancelButton}`}
onClick={onImgCancel}
>
Cancel
</button>
<button
className={`${styles.button} ${styles.doneButton}`}
onClick={this.onImgDone}
>
Done
</button>
</div>
</div>
</div>
)}
</div>
</Modal>
)
@ -132,10 +307,21 @@ Submit.propTypes = {
url: PropTypes.string.isRequired,
img: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
imgControl: PropTypes.bool.isRequired,
imgControlZoom: PropTypes.number.isRequired,
imgControlMove: PropTypes.bool.isRequired,
imgControlX: PropTypes.number.isRequired,
imgControlY: PropTypes.number.isRequired,
onClickClose: PropTypes.func.isRequired,
onInputName: PropTypes.func.isRequired,
onInputDesc: PropTypes.func.isRequired,
onInputUrl: PropTypes.func.isRequired,
onImgRead: PropTypes.func.isRequired,
onImgZoom: PropTypes.func.isRequired,
onImgMoveControl: PropTypes.func.isRequired,
onImgMove: PropTypes.func.isRequired,
onImgCancel: PropTypes.func.isRequired,
onImgDone: PropTypes.func.isRequired,
}
export default Submit

View File

@ -13,6 +13,24 @@
}
}
.modalContentImgControl {
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
@media (min-width: $desktop) {
.modalContentImgControl {
height: 512px;
}
}
.cntWithImgControl {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.title {
line-height: 40px;
text-align: center;
@ -21,6 +39,10 @@
border-bottom: 1px solid #f7f7f7;
}
.withImgControl {
display: none;
}
.block {
padding: 0 16px 16px 16px;
@ -67,7 +89,6 @@
border: 1px dashed #939ba1;
margin: 16px auto;
background: #eef2f5;
cursor: pointer;
span {
color: #939ba1;
@ -80,10 +101,20 @@
position: absolute;
left: 0;
top: 0;
border-radius: initial;
border-radius: inherit;
background-size: cover;
background-position: center;
}
input.uploader {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
}
.imgInfo {
@ -125,6 +156,11 @@
line-height: 22px;
color: #939ba1;
font-size: 15px;
a {
color: $link-color;
text-decoration: none;
}
}
.submitButton {
@ -143,3 +179,88 @@
background: $text-color;
}
}
.imgControl {
display: flex;
align-items: center;
flex-direction: column;
flex: 1 1 auto;
.imgCanvasCnt {
width: 100%;
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: center;
.imgCanvas {
width: 160px;
height: 160px;
border-radius: 50%;
pointer-events: none;
}
}
.controls {
width: 100%;
border-top: 1px solid #eef2f5;
.slider {
height: 74px;
display: flex;
align-items: center;
* {
flex: 1 1 auto;
}
.minZoom {
width: 10px;
height: 10px;
flex: 0 0 auto;
border: 1px solid #939ba1;
border-radius: 3px;
margin: auto 30px auto 15px;
}
.maxZoom {
width: 18px;
height: 18px;
flex: 0 0 auto;
border: 1px solid #939ba1;
border-radius: 3px;
margin: auto 11px auto 26px;
}
}
.actionsCnt {
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 0 16px;
.button {
height: 44px;
border-radius: 8px;
border: none;
border-radius: 22px;
font-family: $font;
padding: calculateRem(11) calculateRem(38);
font-size: calculateRem(15);
cursor: pointer;
}
.cancelButton {
color: $link-color;
background: #eceffc;
}
.doneButton {
color: #fff;
background: $link-color;
}
}
}
}

View File

@ -7,6 +7,12 @@ const ON_INPUT_NAME = 'ON_INPUT_NAME'
const ON_INPUT_DESC = 'ON_INPUT_DESC'
const ON_INPUT_URL = 'ON_INPUT_URL'
const ON_SELECT_CATEGORY = 'ON_SELECT_CATEGORY'
const ON_IMG_READ = 'ON_IMG_READ'
const ON_IMG_ZOOM = 'ON_IMG_ZOOM'
const ON_IMG_MOVE_CONTROL = 'ON_IMG_MOVE_CONTROL'
const ON_IMG_MOVE = 'ON_IMG_MOVE'
const ON_IMG_CANCEL = 'ON_IMG_CANCEL'
const ON_IMG_DONE = 'ON_IMG_DONE'
export const showSubmitAction = () => {
window.location.hash = 'submit'
@ -44,9 +50,45 @@ export const onSelectCategoryAction = category => ({
payload: category,
})
export const onImgReadAction = imgBase64 => ({
type: ON_IMG_READ,
payload: imgBase64,
})
export const onImgZoomAction = zoom => ({
type: ON_IMG_ZOOM,
payload: zoom,
})
export const onImgMoveControlAction = move => ({
type: ON_IMG_MOVE_CONTROL,
payload: move,
})
export const onImgMoveAction = (x, y) => ({
type: ON_IMG_MOVE,
payload: { x, y },
})
export const onImgCancelAction = () => ({
type: ON_IMG_CANCEL,
payload: null,
})
export const onImgDoneAction = imgBase64 => ({
type: ON_IMG_DONE,
payload: imgBase64,
})
const showSubmit = state => {
return Object.assign({}, state, {
visible: true,
img: '',
imgControl: false,
imgControlZoom: 0,
imgControlMove: false,
imgControlX: 0,
imgControlY: 0,
})
}
@ -80,6 +122,50 @@ const onSelectCategory = (state, category) => {
})
}
const onImgRead = (state, imgBase64) => {
return Object.assign({}, state, {
img: imgBase64,
imgControl: true,
imgControlZoom: 0,
imgControlMove: false,
imgControlX: 0,
imgControlY: 0,
})
}
const onImgZoom = (state, zoom) => {
return Object.assign({}, state, {
imgControlZoom: zoom,
})
}
const onImgMoveControl = (state, move) => {
return Object.assign({}, state, {
imgControlMove: move,
})
}
const onImgMove = (state, payload) => {
return Object.assign({}, state, {
imgControlX: payload.x,
imgControlY: payload.y,
})
}
const onImgCancel = state => {
return Object.assign({}, state, {
img: '',
imgControl: false,
})
}
const onImgDone = (state, imgBase64) => {
return Object.assign({}, state, {
img: imgBase64,
imgControl: false,
})
}
const map = {
[SHOW_SUBMIT]: showSubmit,
[CLOSE_SUBMIT]: closeSubmit,
@ -87,6 +173,12 @@ const map = {
[ON_INPUT_DESC]: onInputDesc,
[ON_INPUT_URL]: onInputUrl,
[ON_SELECT_CATEGORY]: onSelectCategory,
[ON_IMG_READ]: onImgRead,
[ON_IMG_ZOOM]: onImgZoom,
[ON_IMG_MOVE_CONTROL]: onImgMoveControl,
[ON_IMG_MOVE]: onImgMove,
[ON_IMG_CANCEL]: onImgCancel,
[ON_IMG_DONE]: onImgDone,
}
export default reducerUtil(map, submitInitialState)

View File

@ -0,0 +1,13 @@
import { connect } from 'react-redux'
import TransactionStatus from './TransactionStatus'
import { hideAction } from './TransactionStatus.recuder'
const mapStateToProps = state => state.transactionStatus
const mapDispatchToProps = dispatch => ({
hide: () => dispatch(hideAction()),
})
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TransactionStatus)

View File

@ -0,0 +1,68 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactImageFallback from 'react-image-fallback'
import styles from './TransactionStatus.module.scss'
import icon from '../../common/assets/images/icon.svg'
import loadingSpinner from '../../common/assets/images/loading-spinner.svg'
class TransactionStatus extends React.Component {
componentDidMount() {
this.checkPublished()
}
componentDidUpdate() {
this.checkPublished()
}
checkPublished() {
const { published, hide } = this.props
if (published) {
setTimeout(() => {
hide()
}, 1000)
}
}
render() {
const { dappName, dappUrl, published, progress } = this.props
return (
<div className={`${styles.cnt} ${dappName !== '' ? styles.active : ''}`}>
<ReactImageFallback
className={styles.image}
src={dappUrl}
fallbackImage={icon}
alt="App icon"
/>
<div className={styles.data}>
<div className={styles.name}>{dappName}</div>
<div className={styles.info}>
Status is an open source mobile DApp browser and messenger build for
#Etherium
</div>
{published && <div className={styles.status}> Published</div>}
{progress && (
<div className={styles.status}>
<img src={loadingSpinner} />
Waiting for confirmation of the network...
</div>
)}
</div>
</div>
)
}
}
TransactionStatus.defaultProps = {
dapp: null,
}
TransactionStatus.propTypes = {
dappName: PropTypes.string.isRequired,
dappUrl: PropTypes.string.isRequired,
progress: PropTypes.bool.isRequired,
published: PropTypes.bool.isRequired,
hide: PropTypes.func.isRequired,
}
export default TransactionStatus

View File

@ -0,0 +1,78 @@
@import '../../common/styles/variables';
// @keyframes loading_rotate {
// 100% {
// transform: rotate(360deg);
// }
// }
.cnt {
width: 100%;
display: flex;
box-sizing: border-box;
position: fixed;
left: 0;
top: 0;
font-family: $font;
padding: 12px;
background: #fff;
box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08),
0px 2px 4px rgba(0, 34, 51, 0.16);
transform: translateY(-200%);
transition-property: transform;
transition-duration: 0.4s;
.image {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 16px;
}
.data {
display: flex;
flex-direction: column;
.name {
line-height: 22px;
font-size: 15px;
font-weight: 700;
margin-bottom: 2px;
}
.info {
line-height: 18px;
color: #939ba1;
font-size: 13px;
margin-bottom: 2px;
}
.status {
display: flex;
align-items: center;
line-height: 15px;
color: $link-color;
font-size: 12px;
img {
margin-right: 4px;
// animation:loading_rotate 1s linear infinite;
}
}
}
}
.cnt.active {
transform: translateY(0);
}
@media (min-width: $desktop) {
.cnt {
width: 375px;
right: 0;
top: 16px;
border-radius: 16px;
margin: auto;
}
}

View File

@ -0,0 +1,21 @@
import transactionStatusInitialState from '../../common/data/transaction-status'
import reducerUtil from '../../common/utils/reducer'
const HIDE = 'HIDE'
export const hideAction = () => ({
type: HIDE,
payload: null,
})
const hide = state => {
return Object.assign({}, state, {
dappName: '',
})
}
const map = {
[HIDE]: hide,
}
export default reducerUtil(map, transactionStatusInitialState)

View File

@ -0,0 +1,3 @@
import TransactionStatus from './TransactionStatus.container'
export default TransactionStatus