}
19 |
20 | );
21 | };
22 |
23 | export default ScreenMask;
--------------------------------------------------------------------------------
/bitimulate-backend/src/api/v1.0/auth/index.js:
--------------------------------------------------------------------------------
1 | const Router = require('koa-router');
2 |
3 | const auth = new Router();
4 | const authCtrl = require('./auth.ctrl');
5 |
6 | auth.get('/exists/email/:email', authCtrl.checkEmail);
7 | auth.get('/exists/display-name/', authCtrl.checkDisplayName);
8 | auth.get('/exists/display-name/:displayName', authCtrl.checkDisplayName);
9 | auth.post('/register/local', authCtrl.localRegister);
10 | auth.post('/register/:provider(facebook|google)', authCtrl.socialRegister);
11 | auth.post('/login/local', authCtrl.localLogin);
12 | auth.post('/login/:provider(facebook|google)', authCtrl.socialLogin);
13 | auth.get('/check', authCtrl.check);
14 | auth.post('/logout', authCtrl.logout);
15 |
16 | module.exports = auth;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Dimmer/Dimmer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './Dimmer.scss';
3 | import classNames from 'classnames/bind';
4 |
5 | const cx = classNames.bind(styles);
6 |
7 | class Dimmer extends Component {
8 | componentDidMount() {
9 | // hides scroll-y
10 | document.body.style.overflowY = 'hidden';
11 | }
12 |
13 | componentWillUnmount() {
14 | // shows scroll-y
15 | document.body.style.overflowY = 'auto';
16 | }
17 |
18 | render() {
19 | const { ...rest } = this.props;
20 |
21 | return (
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | }
29 |
30 | export default Dimmer;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/TradeIndexSubpage/TradeIndexSubpage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './TradeIndexSubpage.scss';
3 | import classNames from 'classnames/bind';
4 | import { TradeIndexContainer, TradeIndexOptionsContainer } from 'containers';
5 | import { Helmet } from 'react-helmet';
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | const TradeIndexSubpage = () => {
10 | return (
11 |
12 |
13 | 거래소 :: Bitimulate
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default TradeIndexSubpage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/ScreenMaskContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { ScreenMask } from 'components';
4 |
5 |
6 | class ScreenMaskContainer extends Component {
7 |
8 | componentDidUpdate(prevProps, prevState) {
9 | if(prevProps.visible !== this.props.visible) {
10 | // if visible value is changed, hide or show the scrollbar
11 | document.body.style.overflowY = this.props.visible ? 'hidden' : 'auto';
12 | }
13 | }
14 |
15 |
16 | render() {
17 | return (
)
18 | }
19 | }
20 |
21 | export default connect(
22 | (state) => ({
23 | visible: state.base.getIn(['screenMask', 'visible'])
24 | })
25 | )(ScreenMaskContainer);
26 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/BgColor/BgColor.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './BgColor.scss';
3 | import classNames from 'classnames/bind';
4 |
5 | const cx = classNames.bind(styles);
6 |
7 | class BgColor extends Component {
8 | previousColor = null;
9 |
10 | setBackgroundColor = (color) => {
11 | document.body.style.background = color;
12 | }
13 |
14 | componentDidMount() {
15 | const { color } = this.props;
16 | this.previousColor = document.body.style.background;
17 | this.setBackgroundColor(color);
18 | }
19 |
20 | componentWillUnmount() {
21 | this.setBackgroundColor(this.previousColor);
22 | }
23 |
24 | render() {
25 | return
;
26 | }
27 | }
28 |
29 | export default BgColor;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Option/Option.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Option.scss';
3 | import classNames from 'classnames/bind';
4 | import BlankIcon from 'react-icons/lib/md/check-box-outline-blank';
5 | import CheckBoxIcon from 'react-icons/lib/md/check-box';
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | const Option = ({children, active, onClick}) => {
10 | return (
11 |
14 |
15 |
16 |
17 |
18 |
{children}
19 |
20 | );
21 | };
22 |
23 | export default Option;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Spinner/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Spinner = ({size="200px", color="currentColor", ...rest}) => {
4 | return (
5 |
10 | );
11 | };
12 |
13 | export default Spinner;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/log.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const Moment = require('moment');
3 |
4 | function getTime() {
5 | const now = new Moment();
6 | const time = chalk.dim(`[${now.format('HH:mm:ss')}]`);
7 | return time;
8 | }
9 |
10 | function log(...message) {
11 | const time = getTime();
12 | const type = chalk.bold('[LOG]');
13 | console.log(`${time}${type}`, ...message);
14 | }
15 |
16 | log.info = (...message) => {
17 | const time = getTime();
18 | const type = chalk.bold(chalk.cyan('[INFO]'));
19 | console.info(`${time}${type}`, ...message);
20 | };
21 |
22 | log.error = (...message) => {
23 | const time = getTime();
24 | const type = chalk.bold(chalk.red('[ERROR]'));
25 | console.error(`${time}${type}`, ...message);
26 | };
27 |
28 | module.exports = log;
--------------------------------------------------------------------------------
/bitimulate-backend/src/db/models/EarningsHistory.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | require('mongoose-double')(mongoose);
3 | const User = require('./User');
4 |
5 | const { Schema } = mongoose;
6 | // const { Types } = Schema;
7 |
8 | const EarningsHistory = new Schema({
9 | ratio: Schema.Types.Double,
10 | userId: {
11 | type: Schema.Types.ObjectId,
12 | ref: User
13 | }
14 | }, { timestamps: true });
15 |
16 | EarningsHistory.index({ createdAt: 1 }, {expireAfterSeconds: 60 * 60 * 24 * 30});
17 | EarningsHistory.index({ userId: 1 });
18 |
19 | EarningsHistory.statics.create = function(userId, ratio) {
20 | const earnings = new this({
21 | userId,
22 | ratio
23 | });
24 |
25 | return earnings.save();
26 | };
27 |
28 | module.exports = mongoose.model('EarningsHistory', EarningsHistory);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/SocialLoginButton/SocialLoginButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './SocialLoginButton.scss';
3 | import classNames from 'classnames/bind';
4 | import FacebookIcon from 'react-icons/lib/io/social-facebook';
5 | import GoogleIcon from 'react-icons/lib/io/social-googleplus';
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | const SocialLoginButton = ({onSocialLogin}) => {
10 | return (
11 |
12 |
{onSocialLogin('facebook')}}>
13 |
14 |
15 |
{onSocialLogin('google')}}>
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default SocialLoginButton;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/CoinMain/CoinMain.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './CoinMain.scss';
3 | import classNames from 'classnames/bind';
4 | import { CoinBlock } from 'components';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | // const coinTypes = ['BTC', 'BCH', 'ETH', 'LTC', 'XRP', 'DASH', 'XMR']
9 | const CoinMain = ({
10 | rate
11 | }) => {
12 | const coinBlockList = rate.map(
13 | r => (
)
20 | );
21 |
22 | return (
23 |
24 | {coinBlockList}
25 |
26 | );
27 | }
28 |
29 | export default CoinMain;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/token.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const { JWT_SECRET: secret } = process.env;
3 |
4 | function generateToken(payload, subject) {
5 | return new Promise(
6 | (resolve, reject) => {
7 | jwt.sign(payload, secret, {
8 | issuer: 'bitimulate.com',
9 | expiresIn: '7d',
10 | subject
11 | }, (error, token) => {
12 | if(error) reject(error);
13 | resolve(token);
14 | });
15 | }
16 | );
17 | }
18 |
19 | function decodeToken(token) {
20 | return new Promise(
21 | (resolve, reject) => {
22 | jwt.verify(token, secret, (error, decoded) => {
23 | if(error) reject(error);
24 | resolve(decoded);
25 | });
26 | }
27 | );
28 | }
29 |
30 | exports.generateToken = generateToken;
31 | exports.decodeToken = decodeToken;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/WalletMenu/WalletMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WalletMenu.scss';
3 | import classNames from 'classnames/bind';
4 | import { Card, ResponsiveAd } from 'components';
5 | import { NavLink } from 'react-router-dom';
6 |
7 | const cx = classNames.bind(styles);
8 |
9 |
10 | const WalletMenu = () => {
11 | return (
12 |
13 | 내 지갑
14 | 거래내역
15 | 수익률
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default WalletMenu;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/UserButton/UserButton.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .user-button {
4 | user-select: none;
5 | margin-left: 1rem;
6 | display: flex;
7 | flex-direction: row;
8 | align-items: center;
9 | color: white;
10 | padding-top: 0.4rem;
11 | padding-bottom: 0.4rem;
12 | padding-left: 0.8rem;
13 | padding-right: 0.8rem;
14 | border: 2px solid rgba(255,255,255,0.3);
15 | transition: all .3s;
16 | cursor: pointer;
17 | svg {
18 | font-size: 1.25rem;
19 | margin-right: 0.25rem;
20 | }
21 | .display-name {
22 | font-weight: 700;
23 | }
24 | &:hover {
25 | background: white;
26 | color: material-color('teal', '600');
27 | border: 2px solid white;
28 | @include material-shadow(2);
29 | }
30 | &:active {
31 | background: material-color('grey', '200');
32 | }
33 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/Wallets/Wallets.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Wallets.scss';
3 | import classNames from 'classnames/bind';
4 | import { TripleWallet, WalletTable } from 'components';
5 | const cx = classNames.bind(styles);
6 |
7 | const Wallets = ({
8 | sum,
9 | krwRate,
10 | btcMultiplier,
11 | walletData,
12 | hideName
13 | }) => (
14 |
15 |
16 |
17 | 현재 총합 보유 자산
18 |
19 |
24 |
25 |
29 |
30 | );
31 |
32 | export default Wallets;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/templates/PageTemplate/PageTemplate.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './PageTemplate.scss';
3 | import classNames from 'classnames/bind';
4 | import { SidebarContainer } from 'containers';
5 |
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | const PageTemplate = ({header, children, responsive, padding, mobileNoPadding}) => {
10 | return (
11 |
12 |
15 | { header && }
16 |
17 |
22 | {children}
23 |
24 |
25 | );
26 | };
27 |
28 | export default PageTemplate;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/middlewares/jwt.js:
--------------------------------------------------------------------------------
1 | const { generateToken, decodeToken } = require('../token');
2 |
3 | module.exports = async (ctx, next) => {
4 | const token = ctx.cookies.get('access_token');
5 | if(!token) {
6 | // if there is no token, skip!
7 | ctx.request.user = null;
8 | return next();
9 | }
10 |
11 | try {
12 | const decoded = await decodeToken(token);
13 | const { user } = decoded;
14 | // re-issue token when its age is over 3 days
15 | if(Date.now() / 1000 - decoded.iat > 60 * 60 * 24 * 3) {
16 | const freshToken = await generateToken({ user }, 'user');
17 | ctx.cookies.set('access_token', freshToken, {
18 | maxAge: 1000 * 60 * 60 * 24 * 7
19 | });
20 | }
21 | ctx.request.user = user;
22 | } catch (e) {
23 | ctx.request.user = null;
24 | }
25 |
26 | return next();
27 | };
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/TradePage/TradePage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PageTemplate, TradeIndexSubpage, TradeDetailSubpage, ResponsiveAd } from 'components';
3 | import { HeaderContainer } from 'containers';
4 | import { Route } from 'react-router-dom';
5 | import classNames from 'classnames/bind';
6 |
7 | import styles from './TradePage.scss';
8 | const cx = classNames.bind(styles);
9 |
10 |
11 | const TradePage = ({match}) => {
12 | return (
13 |
} padding responsive>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | };
23 |
24 | export default TradePage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/RewardPage/RewardPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './RewardPage.scss';
3 | import classNames from 'classnames/bind';
4 | import { PageTemplate } from 'components';
5 | import { HeaderContainer, RewardWalletFormContainer } from 'containers';
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | const RewardPage = () => (
10 | } padding responsive>
11 |
12 |
상금 지급용 지갑 설정
13 |
14 |
매달 월 수익률 랭킹 1위에게 상금이 리플(XRP)로 지급됩니다.
15 |
상급을 지급받을 리플 지갑 정보를 입력하세요.
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | export default RewardPage;
--------------------------------------------------------------------------------
/bitimulate-frontend/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/TradeHistory/TradeHistory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './TradeHistory.scss';
3 | import classNames from 'classnames/bind';
4 | import { Card, TradeHistoryTable } from 'components';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | const TradeHistory = ({historyData, privateOrders, onCancelOrder, onScroll, hasNext}) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default TradeHistory;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/TradeIndexOptions/TradeIndexOptions.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .options {
4 | margin-top: 1rem;
5 | padding: 1rem;
6 | background: white;
7 | display: flex;
8 | flex-direction: row;
9 | align-items: center;
10 | @include material-shadow(1, 0.5);
11 |
12 | @include media(" {
9 | return (
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default OrderBook;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/styles/lib/mixins/_material-shadow.scss:
--------------------------------------------------------------------------------
1 | @mixin material-shadow($z-depth: 1, $strength: 1, $color: black) {
2 | @if $z-depth == 1 {
3 | box-shadow: 0 1px 3px rgba($color, $strength * 0.14), 0 1px 2px rgba($color, $strength * 0.24);
4 | }
5 | @if $z-depth == 2 {
6 | box-shadow: 0 3px 6px rgba($color, $strength * 0.16), 0 3px 6px rgba($color, $strength * 0.23);
7 | }
8 | @if $z-depth == 3 {
9 | box-shadow: 0 10px 20px rgba($color, $strength * 0.19), 0 6px 6px rgba($color, $strength * 0.23);
10 | }
11 | @if $z-depth == 4 {
12 | box-shadow: 0 15px 30px rgba($color, $strength * 0.25), 0 10px 10px rgba($color, $strength * 0.22);
13 | }
14 | @if $z-depth == 5{
15 | box-shadow: 0 20px 40px rgba($color, $strength * 0.30), 0 15px 12px rgba($color, $strength * 0.22);
16 | }
17 | @if ($z-depth < 1) or ($z-depth > 5) {
18 | @warn "$z-depth must be between 1 and 5";
19 | }
20 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/CoinMainContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import {bindActionCreators} from 'redux';
4 | import { CoinMain, SpinnerBlock } from 'components';
5 | import { coinsInHome } from 'lib/variables'
6 |
7 | class CoinMainContainer extends Component {
8 | render() {
9 | const { rate } = this.props;
10 |
11 | if(rate.size === 0) {
12 | return
13 | }
14 |
15 |
16 | const filteredRate = rate.filter(
17 | r => coinsInHome.indexOf(r.get('currencyKey')) !== -1
18 | ).sort((a,b) => coinsInHome.indexOf(a.get('currencyKey')) - coinsInHome.indexOf(b.get('currencyKey')));
19 |
20 | return (
21 |
22 | )
23 | }
24 | }
25 |
26 | export default connect(
27 | (state) => ({
28 | rate: state.trade.get('rate')
29 | }),
30 | (dispatch) => ({
31 | })
32 | )(CoinMainContainer);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/TripleWallet/TripleWallet.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './TripleWallet.scss';
3 | import classNames from 'classnames/bind';
4 | import { limitDigit } from 'lib/utils';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | const WalletBox = ({value, currency, sign}) => (
9 |
10 |
11 | {currency}
12 |
13 |
14 | {sign} {limitDigit(value, 10, true, currency !== 'BTC')}
15 |
16 |
17 | )
18 |
19 | const TripleWallet = ({btc, usd, krw}) => {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default TripleWallet;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/RewardWalletForm/RewardWalletForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './RewardWalletForm.scss';
3 | import classNames from 'classnames/bind';
4 | import { Input, Card, Button } from 'components';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | const RewardWalletForm = ({
9 | address,
10 | destinationTag,
11 | onChange,
12 | onSubmit
13 | }) => (
14 |
15 | 리플 지갑
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | export default RewardWalletForm;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/Sidebar/Sidebar.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .upper-block {
4 | height: 12rem;
5 | background: #272727;
6 | box-shadow: inset 0px -8px 8px -8px rgba(0, 0, 0, 0.4);
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | flex-direction: column;
11 | padding-top: 2rem;
12 | .message {
13 | font-weight: 200;
14 | text-align: center;
15 | color: white;
16 | font-size: 1.25rem;
17 | }
18 | .sign-button {
19 | margin-top: 1rem;
20 | }
21 | }
22 |
23 | .menu {
24 | display: flex;
25 | flex-direction: column;
26 | border-bottom: 1px solid rgb(85, 85, 85);
27 | .menu-item {
28 | padding: 1rem;
29 | padding-left: 1.5rem;
30 | color: white;
31 | font-size: 1.125rem;
32 | &:active {
33 | background: rgb(40, 40, 40);
34 | }
35 | }
36 | .menu-item + .menu-item {
37 | border-top: 1px solid rgb(85, 85, 85);
38 | }
39 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/store/modules/ranking.js:
--------------------------------------------------------------------------------
1 | import { createAction, handleActions } from 'redux-actions';
2 |
3 | import { Map, List, fromJS } from 'immutable';
4 | import { pender } from 'redux-pender';
5 |
6 | import * as CommonAPI from 'lib/api/common';
7 |
8 |
9 | // action types
10 | const GET_TOP_RANKING = 'ranking/GET_TOP_RANKING';
11 |
12 | // action creator
13 | export const getTopRanking = createAction(GET_TOP_RANKING, CommonAPI.getTopRanking);
14 |
15 | // initial state
16 | const initialState = Map({
17 | ranking: List(),
18 | count: 0,
19 | me: 0
20 | });
21 |
22 | // reducer
23 | export default handleActions({
24 | ...pender({
25 | type: GET_TOP_RANKING,
26 | onSuccess: (state, action) => {
27 | const { ranking, count, me } = action.payload.data;
28 | return state.set('ranking', fromJS(ranking))
29 | .set('count', count)
30 | .set('me', me);
31 | }
32 | })
33 | }, initialState);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/Core.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | // import redux dependencies
4 | import { connect } from 'react-redux';
5 | import {bindActionCreators} from 'redux';
6 | import * as commonActions from 'store/modules/common';
7 | import * as tradeActions from 'store/modules/trade';
8 | import { MsgboxContainer } from 'containers';
9 |
10 | class Core extends Component {
11 | componentDidMount() {
12 | const { CommonActions, TradeActions } = this.props;
13 | CommonActions.getCurrencyInfo();
14 | TradeActions.getInitialRate();
15 | }
16 |
17 | render() {
18 | return
19 |
20 |
21 | }
22 | }
23 |
24 | export default connect(
25 | (state) => ({
26 |
27 | }),
28 | (dispatch) => ({
29 | CommonActions: bindActionCreators(commonActions, dispatch),
30 | TradeActions: bindActionCreators(tradeActions, dispatch)
31 | })
32 | )(Core);
33 |
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/social.js:
--------------------------------------------------------------------------------
1 | const FB = require('fb');
2 | const google = require('googleapis');
3 |
4 | const plus = google.plus('v1');
5 |
6 | function getFacebookProfile(accessToken) {
7 | return FB.api('me', { fields: ['email'], access_token: accessToken }).then(
8 | (auth) => ({
9 | id: auth.id,
10 | email: auth.email
11 | })
12 | );
13 | }
14 |
15 | function getGoogleProfile(accessToken) {
16 | return new Promise((resolve, reject) => {
17 | plus.people.get({
18 | userId: 'me',
19 | access_token: accessToken
20 | }, (err, auth) => {
21 | if(err) reject(err);
22 | resolve({
23 | id: auth.id,
24 | email: auth.emails[0].value
25 | });
26 | });
27 | });
28 | }
29 |
30 | exports.getProfile = (provider, accessToken) => {
31 | const getters = {
32 | google: getGoogleProfile,
33 | facebook: getFacebookProfile
34 | };
35 |
36 | return getters[provider](accessToken);
37 | };
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Modal/Modal.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .enter {
4 | animation-duration: .4s;
5 | animation-fill-mode: both;
6 | animation-timing-function: ease-in-out;
7 | animation-name: bounceUpIn;
8 | }
9 |
10 | .leave {
11 | animation-duration: .4s;
12 | animation-fill-mode: both;
13 | animation-timing-function: ease-in-out;
14 | animation-name: bounceDownOut;
15 | }
16 |
17 | .modal-wrapper {
18 | // places the modal at center
19 | position: fixed;
20 | top: 50%;
21 | left: 50%;
22 | transform: translate(-50%, -50%);
23 | z-index: z-index-for('modal');
24 |
25 | &.uncenter {
26 | top: 1rem;
27 | transform: translate(-50%);
28 | }
29 |
30 | &.mobile-fullscreen {
31 | @include media(" {
12 | const multipliers = [1, 10, 100];
13 | return multipliers.map(
14 | multiplier => `${info.symbol} ${(info.initialValue * multiplier).toLocaleString()}`
15 | )
16 | })();
17 | const optionList = options.map(
18 | (option, i) => (
19 |
20 | )
21 | )
22 | return (
23 |
24 | {optionList}
25 |
26 | );
27 | }
28 | }
29 |
30 |
31 | export default InitialMoneyOptions;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './Modal.scss';
3 | import classNames from 'classnames/bind';
4 | import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
5 |
6 |
7 | const cx = classNames.bind(styles);
8 |
9 | class ModalWrapper extends Component {
10 | render() {
11 | const { visible, children, mobileFullscreen } = this.props;
12 |
13 | return (
14 |
15 |
22 | { visible &&
23 | {children}
24 |
}
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | export default ModalWrapper;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/ButtonSelector/ButtonSelector.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 |
4 | .button-selector {
5 | display: flex;
6 | flex-direction: row;
7 |
8 | .option {
9 | padding-top: 0.5rem;
10 | padding-bottom: 0.5rem;
11 | padding-left: 0.5rem;
12 | padding-right: 0.5rem;
13 | font-size: 0.75rem;
14 | background: material-color('grey', '100');
15 | color: material-color('grey', '400');
16 | border-radius: 4px;
17 | transition: box-shadow 0.15s;
18 | user-select: none;
19 | cursor: pointer;
20 | &:hover {
21 | background: white;
22 | @include material-shadow(1, 0.25);
23 | color: material-color('grey', '600');
24 | }
25 | &.active {
26 | transform: scale(1.1, 1.1);
27 | @include material-shadow(2, 0.5);
28 | font-weight: bold;
29 | background: white;
30 | color: material-color('cyan', '600');
31 | }
32 | }
33 |
34 | .option + .option {
35 | margin-left: 0.5rem;
36 | }
37 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/MsgboxContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {Msgbox} from 'components'
3 | import * as baseActions from 'store/modules/base';
4 | import { bindActionCreators } from 'redux';
5 | import { connect } from 'react-redux';
6 |
7 |
8 | class MsgboxContainer extends Component {
9 | handleExit = () => {
10 | const { BaseActions } = this.props;
11 | BaseActions.hideMsgbox();
12 | }
13 |
14 | render() {
15 | const { msgbox } = this.props;
16 | const {
17 | text, type, visible
18 | } = msgbox.toJS();
19 | const { handleExit } = this;
20 |
21 | return (
22 |
28 | );
29 | }
30 | }
31 |
32 | export default connect(
33 | (state) => ({
34 | msgbox: state.base.get('msgbox')
35 | }),
36 | (dispatch) => ({
37 | BaseActions: bindActionCreators(baseActions, dispatch)
38 | })
39 | )(MsgboxContainer);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/lib/social.js:
--------------------------------------------------------------------------------
1 | import hello from 'hellojs';
2 |
3 | hello.init({
4 | facebook: 1456738254380084,
5 | google: '139700894213-90pmhsv3jrjaoln83f353fmjvspdibb9.apps.googleusercontent.com'
6 | }, {redirect_uri: '/redirect.html'});
7 |
8 | export default(function () {
9 | return {
10 | facebook: () => {
11 | return new Promise((resolve, reject) => {
12 | // hellojs 는 일반 Promise 가 아닌 Promise A+ 를 사용하므로, Promise 로 감싸줌
13 | hello.login('facebook', { scope: 'email' }).then(
14 | auth => resolve(auth.authResponse.access_token),
15 | e => reject(e)
16 | );
17 | })
18 | },
19 | google: () => {
20 | return new Promise((resolve, reject) => {
21 | hello.login('google', { scope: 'email' }).then(
22 | auth => resolve(auth.authResponse.access_token),
23 | e => reject(e)
24 | );
25 | })
26 | }
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/SelectCurrency/SelectCurrency.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './SelectCurrency.scss';
3 | import classNames from 'classnames/bind';
4 | import { initialCurrencies } from 'lib/variables';
5 |
6 | const currencies = initialCurrencies;
7 |
8 | const cx = classNames.bind(styles);
9 |
10 | const Currency = ({children, active, symbol, onClick}) => (
11 |
12 |
{symbol}
13 |
{children}
14 |
15 | )
16 |
17 | const SelectCurrency = ({currency, onSetCurrency}) => {
18 | const currencyList = currencies.map(
19 | c => (
20 | onSetCurrency(c.name)}
23 | key={c.name}>
24 | {c.name}
25 |
26 | )
27 | );
28 |
29 | return (
30 |
31 | {currencyList}
32 |
33 | );
34 | };
35 |
36 | export default SelectCurrency;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/SelectCurrency/SelectCurrency.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .select-currency {
4 | display: flex;
5 | .currency {
6 | border: 1px solid transparent;
7 | display: flex;
8 | align-items: center;
9 | padding: 1rem;
10 | width: 8rem;
11 | padding-top: 0.5rem;
12 | padding-bottom: 0.5rem;
13 | border-radius: 2px;
14 | transition: all .3s;
15 | background: material-color('grey', '200');
16 | border-radius: 2px;
17 | cursor: pointer;
18 | font-weight: 700;
19 | &:hover {
20 | @include material-shadow(2);
21 | background: material-color('grey', '100');
22 | }
23 | &.active, &:active {
24 | border: 1px solid material-color('teal', '500');
25 | background: white;
26 | color: material-color('teal', '500');
27 | }
28 | .symbol {
29 | font-size: 1.25rem;
30 | }
31 | .text {
32 | text-align: center;
33 | flex: 1;
34 | font-size: 0.9rem;
35 | }
36 | }
37 |
38 | .currency + .currency {
39 | margin-left: 1rem;
40 | }
41 | }
--------------------------------------------------------------------------------
/bitimulate-backend/src/crawler/worker.js:
--------------------------------------------------------------------------------
1 | function Worker(works = [], notify) {
2 | this.index = 0;
3 | this.works = works;
4 | this.notify = notify;
5 | }
6 |
7 | Worker.prototype.reset = function() {
8 | this.index = 0;
9 | };
10 |
11 | Worker.prototype.work = function() {
12 | const { works, notify } = this;
13 | if(works.length === 0) return Promise.resolve();
14 |
15 | const promise = new Promise((resolve, reject) => {
16 | const repeat = async () => {
17 | try {
18 | await works[this.index++]();
19 | notify();
20 | } catch (e) {
21 | // console.log(e);
22 | if(e.code !== 'ECONNABORTED') {
23 | console.error(e);
24 | } else {
25 | console.log('timed out');
26 | }
27 | this.index--;
28 | setTimeout(repeat, 1000);
29 | return;
30 | }
31 |
32 | if(this.index >= works.length) {
33 | return resolve();
34 | }
35 | setTimeout(repeat, 1000);
36 | };
37 |
38 | repeat();
39 | });
40 | return promise;
41 | };
42 |
43 | module.exports = Worker;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Button/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Button.scss';
3 | import classNames from 'classnames/bind';
4 |
5 | const cx = classNames.bind(styles);
6 |
7 | const Button = ({
8 | children,
9 | flex,
10 | className,
11 | roundCorner,
12 | invert,
13 | flat,
14 | color,
15 | padding="0.5rem",
16 | xPadding,
17 | style,
18 | disabled,
19 | dark,
20 | onClick,
21 | theme,
22 | ...rest
23 | }) => {
24 | const dynamicStyle = {
25 | ...(xPadding ? {
26 | paddingLeft: xPadding,
27 | paddingRight: xPadding
28 | } : {})
29 | }
30 | return (
31 |
47 | {children}
48 |
49 | );
50 | };
51 |
52 | export default Button;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/OrdersTable/OrdersTable.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | @keyframes flicker {
4 | 50% {
5 | background: rgba(0, 208, 255, 0.15);
6 | }
7 | }
8 |
9 | .orders-table {
10 | .flicker {
11 | animation-duration: .5s;
12 | animation-fill-mode: both;
13 | animation-name: flicker;
14 | }
15 | .title {
16 | font-weight: 600;
17 | color: material-color('grey', '800');
18 | }
19 | .table-head {
20 | margin-bottom: 0.5rem;
21 | margin-top: 1rem;
22 | display: flex;
23 | .col-desc {
24 | color: material-color('grey', '500');
25 | flex: 1;
26 | font-size: 0.85rem;
27 | }
28 | }
29 | .row {
30 | display: flex;
31 | padding-top: 0.25rem;
32 | min-height: 1.4375rem;
33 | padding-bottom: 0.25rem;
34 | .value {
35 | font-size: 0.85rem;
36 | flex: 1;
37 | }
38 | margin-left: -1rem;
39 | margin-right: -1rem;
40 | padding-left: 1rem;
41 | padding-right: 1rem;
42 | &:nth-child(even) {
43 | background: material-color('grey', '100');
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/ReportPage/ReportPage.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .block {
4 | background: linear-gradient(to right, #276caf, #30a1c1);
5 | height: 25rem;
6 | z-index: 1;
7 | @include media(" fn => fn;
10 |
11 | const configureStore = (initialState) => {
12 | const enhancers = [
13 | applyMiddleware(
14 | penderMiddleware()
15 | ),
16 | devtools({
17 | actionsBlacklist: ['trade/UPDATE_TICKER'],
18 | maxAge: 1000
19 | })
20 | ];
21 |
22 | const store = createStore(modules, initialState, compose(...enhancers));
23 |
24 | if(module.hot) {
25 | // module.hot.accept('./modules', () => {
26 | // const nextReducer = require('./modules').default;
27 | // store.replaceReducer(nextReducer);
28 | // });
29 | }
30 |
31 | if(module.hot) {
32 | module.hot.accept('./modules', () => store.replaceReducer(modules));
33 | }
34 |
35 | return store;
36 | };
37 |
38 | export default configureStore;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/TripleWallet/TripleWallet.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .triple-wallet {
4 | margin-top: 1rem;
5 | display: flex;
6 | flex-direction: row;
7 | @include media(" {
12 | const { displayName } = match.params;
13 |
14 | return (
15 | } padding>
16 |
17 | {`${displayName}님의 리포트 :: Bitimulate`}
18 |
19 |
20 |
21 |
22 |
23 | {displayName}님의 리포트
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default ReportPage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/Header/Header.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .header {
4 | height: 3.5rem;
5 | &.solid {
6 | background: linear-gradient(to right, #276caf, #30a1c1);
7 | }
8 | transition: all 0.3s ease-in;
9 | &.shadow {
10 | background: linear-gradient(to right, #276caf, #30a1c1);
11 | @include material-shadow(2, 0.75);
12 | }
13 | // background: material-color('teal', '600');
14 | .responsive {
15 | position: relative;
16 | padding-left: 1rem;
17 | padding-right: 1rem;
18 | @include responsive();
19 | display: flex;
20 | height: 100%;
21 | align-items: center;
22 |
23 | .right-side {
24 | .desktop-only {
25 | display: flex;
26 | @include media("=medium") {
32 | display: none;
33 | }
34 | }
35 | margin-left: auto;
36 | }
37 | }
38 |
39 | .login-button {
40 | margin-left: 1rem;
41 | &:hover {
42 | color: material-color('teal', '500');
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/lib/variables.js:
--------------------------------------------------------------------------------
1 | export const optionsPerCurrency = {
2 | // 'KRW': {
3 | // symbol: '₩',
4 | // initialValue: 1000000
5 | // },
6 | 'USD': {
7 | symbol: '$',
8 | initialValue: 1000
9 | },
10 | 'BTC': {
11 | symbol: 'Ƀ',
12 | initialValue: 1
13 | }
14 | }
15 |
16 | export const initialCurrencies = [
17 | // {
18 | // name: 'KRW',
19 | // symbol: '₩'
20 | // },
21 | {
22 | name: 'USD',
23 | symbol: '$'
24 | },
25 | {
26 | name: 'BTC',
27 | symbol: 'Ƀ'
28 | }
29 | ];
30 |
31 | export const chartTypes = [
32 | {
33 | name: 'day',
34 | text: '하루',
35 | unit: '5분'
36 | },
37 | {
38 | name: 'week',
39 | text: '일주일',
40 | unit: '30분'
41 | },
42 | {
43 | name: 'month',
44 | text: '한달',
45 | unit: '2시간'
46 | },
47 | {
48 | name: 'year',
49 | text: '1년',
50 | unit: '하루'
51 | },
52 | {
53 | name: 'all',
54 | text: '전체',
55 | unit: '하루'
56 | }
57 | ];
58 |
59 | export const coinsInHome = [
60 | 'BTC',
61 | 'BCH',
62 | 'ETH',
63 | 'ETC',
64 | 'LTC',
65 | 'XRP',
66 | 'DASH',
67 | 'XMR'
68 | ];
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/TradeChart/TradeChart.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .trade-chart-wrapper {
4 | margin-top: 2rem;
5 | }
6 |
7 | .currency-head {
8 | padding-top: 0.25rem;
9 | padding-bottom: 0.45rem;
10 | padding-left: 0.65rem;
11 | border-left: 8px solid material-color('cyan', '600');
12 | margin-bottom: 1rem;
13 | .title {
14 | font-size: 2rem;
15 | font-weight: 300;
16 | color: material-color('grey', '900');
17 | }
18 | .desc {
19 | margin-top: 0.25rem;
20 | color: material-color('grey', '700');
21 | font-size: 0.85rem;
22 | }
23 | }
24 |
25 | .trade-chart {
26 | margin-top: 0.5rem;
27 | background: material-color('grey', '200');
28 | @include material-shadow(1,0.8);
29 | height: 450px;
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | position: relative;
34 | overflow: hidden;
35 | .unit {
36 | position: absolute;
37 | right: 0.5rem;
38 | top: 0.5rem;
39 | z-index: 2;
40 | font-size: 0.75rem;
41 | color: material-color('grey', '600');
42 | }
43 | .chart {
44 | width: 100%;
45 | height: 100%;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/Msgbox/Msgbox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './Msgbox.scss';
3 | import classNames from 'classnames/bind';
4 | import { Modal } from 'components';
5 | import CheckIcon from 'react-icons/lib/md/check';
6 | import ErrorIcon from 'react-icons/lib/md/error-outline';
7 | const cx = classNames.bind(styles);
8 |
9 | const icons = {
10 | success: CheckIcon,
11 | error: ErrorIcon,
12 | }
13 |
14 | class Msgbox extends Component {
15 | render() {
16 | const { visible, type = 'success', text, onExit } = this.props;
17 |
18 | const Icon = icons[type];
19 |
20 | return (
21 |
22 |
23 |
24 | { Icon && }
25 |
26 |
27 | {text}
28 |
29 |
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | export default Msgbox;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/store/modules/common.js:
--------------------------------------------------------------------------------
1 | import { createAction, handleActions } from 'redux-actions';
2 |
3 | import { Map, List, fromJS } from 'immutable';
4 | import { pender } from 'redux-pender';
5 | import * as CommonAPI from 'lib/api/common';
6 |
7 | // action types
8 | const GET_CURRENCY_INFO = 'common/CURRENCY_INFO';
9 | const GET_KRW_RATE = 'common/GET_KRW_RATE';
10 |
11 | // action creator
12 | export const getCurrencyInfo = createAction(GET_CURRENCY_INFO, CommonAPI.getCurrencyInfo);
13 | export const getKrwRate = createAction(GET_KRW_RATE, CommonAPI.getKrwRate);
14 |
15 | // initial state
16 | const initialState = Map({
17 | currencyInfo: List(),
18 | krwRate: null
19 | });
20 |
21 | // reducer
22 | export default handleActions({
23 | ...pender({
24 | type: GET_CURRENCY_INFO,
25 | onSuccess: (state, action) => {
26 | const { data: currencyInfo } = action.payload;
27 | return state.set('currencyInfo', fromJS(currencyInfo));
28 | }
29 | }),
30 | ...pender({
31 | type: GET_KRW_RATE,
32 | onSuccess: (state, action) => {
33 | return state.set('krwRate', action.payload.data.KRW);
34 | }
35 | })
36 | }, initialState);
--------------------------------------------------------------------------------
/bitimulate-backend/src/db/models/Order.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | require('mongoose-double')(mongoose);
3 |
4 | const { Schema } = mongoose;
5 | const { Types } = Schema;
6 |
7 | const Order = new Schema({
8 | userId: Types.ObjectId,
9 | currencyPair: String,
10 | price: Schema.Types.Double,
11 | amount: Schema.Types.Double,
12 | processedAmount: {
13 | type: Schema.Types.Double,
14 | default: 0
15 | },
16 | sell: Boolean,
17 | status: {
18 | type: String,
19 | enum: ['waiting', 'partial', 'processed', 'cancelled'],
20 | default: 'waiting'
21 | },
22 | date: {
23 | type: Date,
24 | default: new Date()
25 | },
26 | processedDate: {
27 | type: Date,
28 | default: null
29 | }
30 | });
31 |
32 | Order.statics.findOrders = function(userId, cursor, currencyPair, status) {
33 | return this.find({
34 | userId,
35 | ...(cursor ? { _id: { $lt: cursor } } : {}),
36 | ...(currencyPair ? { currencyPair } : {}),
37 | ...(status ? { status } : {})
38 | }, {
39 | userId: false
40 | }).sort({
41 | _id: -1
42 | }).limit(20).exec();
43 | };
44 |
45 | module.exports = mongoose.model('Order', Order);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/CoinBlock/CoinBlock.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './CoinBlock.scss';
3 | import classNames from 'classnames/bind';
4 | import { CoinIcon, Card } from 'components';
5 | import { limitDigit, decimalToPercentString } from 'lib/utils';
6 | import { Link } from 'react-router-dom';
7 |
8 | const cx = classNames.bind(styles);
9 |
10 | const CoinBlock = ({currencyKey, name , last, percent}) => (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {name}
18 |
19 |
20 |
21 | 현재 시세
22 | ({decimalToPercentString(percent)}%)
23 |
24 |
{limitDigit(last)} {currencyKey === 'BTC' ? 'USD' : 'BTC'}
25 |
26 |
27 |
28 | );
29 |
30 | export default CoinBlock;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/cache.js:
--------------------------------------------------------------------------------
1 | const redis = require('redis');
2 | const bluebird = require('bluebird');
3 |
4 | bluebird.promisifyAll(redis.RedisClient.prototype);
5 | bluebird.promisifyAll(redis.Multi.prototype);
6 |
7 | module.exports = (function() {
8 | const client = redis.createClient();
9 |
10 | return {
11 | get client() {
12 | return client;
13 | },
14 | set(key, value, exp) {
15 | if(exp) {
16 | return client.setAsync(key, JSON.stringify(value), 'EX', exp);
17 | }
18 | return client.setAsync(key, JSON.stringify(value));
19 | },
20 | get(key) {
21 | return client.getAsync(key).then(data => {
22 | if(!data) return null;
23 | return JSON.parse(data);
24 | });
25 | },
26 | cachify(fn, exp, prefix = '') {
27 | return async (...params) => {
28 | const key = `${prefix}${fn.name}:${JSON.stringify(params)}`;
29 | const cached = await this.get(key);
30 | if (cached) {
31 | return cached;
32 | }
33 |
34 | const result = await fn(...params);
35 | this.set(key, result, exp);
36 | return result;
37 | };
38 | }
39 | };
40 | })();
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/ButtonSelector/ButtonSelector.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './ButtonSelector.scss';
3 | import classNames from 'classnames/bind';
4 |
5 | const cx = classNames.bind(styles);
6 |
7 | const Option = ({name, children, active, onClick}) => {
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | const ButtonSelector = ({
16 | options,
17 | onSelect,
18 | value,
19 | className,
20 | ...rest
21 | }) => {
22 | const optionList = options.map(
23 | ({name, text}) => (
24 |
30 | )
31 | );
32 |
33 | return (
34 |
35 | {optionList}
36 |
37 | );
38 | };
39 |
40 | ButtonSelector.defaultProps = {
41 | options: [
42 | {
43 | name: 'value',
44 | text: '텍스트'
45 | },
46 | {
47 | name: 'value2',
48 | text: '텍스트2'
49 | }
50 | ]
51 | }
52 |
53 | export default ButtonSelector;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/PolyBackground/PolyBackground.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .poly-background {
4 | background: linear-gradient(to right bottom,#276caf,#30a1c1,#9dd5ba);
5 | width: 100%;
6 | height: 100vh;
7 | overflow: hidden;
8 | clip-path: polygon(0 0, 100% 0, 100% calc(100% - 128px), 0 100%);
9 | position: relative;
10 | transition: all .7s ease-in-out;
11 | @include media(" {
6 | // ctx.set('Last-Modified', 'Sun, 03 Sep 2017 16:04:24 GMT');
7 | // ctx.set('Cache-Control', 'public, max-age=31536000');
8 | ctx.body = currencyInfo;
9 | };
10 |
11 | exports.getKrwRate = async (ctx) => {
12 | try {
13 | const cached = await getExchangeRate();
14 | ctx.body = cached;
15 | } catch (e) {
16 | ctx.throw(e, 500);
17 | }
18 | };
19 |
20 | exports.getRanking = async (ctx) => {
21 | const { type } = ctx.query;
22 | const { user } = ctx.request;
23 |
24 | let myRank = null;
25 | const monthly = type === 'monthly' || !type;
26 |
27 | try {
28 | const count = await User.count().exec();
29 | const ranking = await User.getTopRanking(monthly);
30 | if(user) {
31 | const u = await User.findById(user._id).exec();
32 | myRank = await u.getRank(monthly);
33 | }
34 | ctx.body = {
35 | count, ranking, me: user && (myRank + 1)
36 | };
37 | } catch (e) {
38 | ctx.throw(e, 500);
39 | }
40 | };
--------------------------------------------------------------------------------
/bitimulate-frontend/src/lib/api/auth.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const checkEmail = (email) => axios.get('/api/v1.0/auth/exists/email/' + email);
4 | export const checkDisplayName = (displayName) => axios.get('/api/v1.0/auth/exists/display-name/' + displayName);
5 | export const localRegister = ({
6 | displayName,
7 | email,
8 | password,
9 | initialMoney: { currency, index }
10 | }) => axios.post('/api/v1.0/auth/register/local', {
11 | displayName,
12 | email,
13 | password,
14 | initialMoney: { currency, index }
15 | })
16 | export const localLogin = ({email, password}) => axios.post('/api/v1.0/auth/login/local', {
17 | email, password
18 | });
19 | export const socialLogin = ({provider, accessToken}) => axios.post('/api/v1.0/auth/login/' + provider, {
20 | accessToken
21 | });
22 | export const socialRegister = ({
23 | displayName,
24 | provider,
25 | accessToken,
26 | initialMoney: { currency, index }
27 | }) => axios.post('/api/v1.0/auth/register/' + provider, {
28 | displayName,
29 | accessToken,
30 | initialMoney: { currency, index }
31 | });
32 | export const checkLoginStatus = () => axios.get('/api/v1.0/auth/check');
33 | export const logout = () => axios.post('/api/v1.0/auth/logout');
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/IntroQuestionContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import {bindActionCreators} from 'redux';
4 | import * as baseActions from 'store/modules/base';
5 | import * as authActions from 'store/modules/auth';
6 | import { IntroQuestion } from 'components';
7 | import { withRouter } from 'react-router-dom';
8 |
9 |
10 | class IntroQuestionContainer extends Component {
11 | handleClick = () => {
12 | const { BaseActions, AuthActions, history, user } = this.props;
13 |
14 | if (user) {
15 | history.push('/trade');
16 | return;
17 | }
18 |
19 | AuthActions.toggleLoginModal();
20 | BaseActions.setScreenMaskVisibility(true);
21 | AuthActions.setModalMode('register');
22 | }
23 | render() {
24 | const { handleClick } = this;
25 | return (
26 |
27 | )
28 | }
29 | }
30 |
31 | export default connect(
32 | (state) => ({
33 | user: state.user.get('user')
34 | }),
35 | (dispatch) => ({
36 | BaseActions: bindActionCreators(baseActions, dispatch),
37 | AuthActions: bindActionCreators(authActions, dispatch)
38 | })
39 | )(withRouter(IntroQuestionContainer));
--------------------------------------------------------------------------------
/bitimulate-frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './Root';
4 | import 'styles/main.scss';
5 | import registerServiceWorker from './registerServiceWorker';
6 | import store from 'store';
7 | import { AppContainer as HotContainer } from 'react-hot-loader';
8 | import social from 'lib/social';
9 | import socket from 'lib/socket';
10 | import axios from 'axios';
11 |
12 | window.axios = axios;
13 | const socketURI = process.env.NODE_ENV === 'production'
14 | ? 'wss://api.bitimulate.com/ws'
15 | : 'ws://localhost:4000/ws'
16 |
17 | if(process.env.NODE_ENV === 'production') {
18 | axios.defaults.withCredentials = true;
19 | axios.defaults.baseURL = 'https://api.bitimulate.com';
20 | }
21 |
22 | console.log(socketURI);
23 | socket.initialize(store, socketURI);
24 |
25 | window.socket = socket;
26 |
27 | const render = (Component) => ReactDOM.render(
28 | (
29 |
30 |
31 |
32 | ),
33 | document.getElementById('root')
34 | );
35 |
36 | render(Root);
37 |
38 | if(module.hot) {
39 | module.hot.accept('./Root', () => render(Root))
40 | }
41 |
42 | registerServiceWorker();
43 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Option/Option.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | @keyframes pop {
4 | 0% {
5 | transform: scale(1, 1);
6 | }
7 | 50% {
8 | transform: scale(1.2, 1.2);
9 | }
10 | 100% {
11 | transform: scale(1, 1);
12 | }
13 | }
14 |
15 |
16 | .option {
17 | cursor: pointer;
18 | display: flex;
19 | align-items: center;
20 | user-select: none;
21 |
22 | .check-box {
23 | margin-right: 0.5rem;
24 | font-size: 1.25rem;
25 | .checked {
26 | display: none;
27 | }
28 | }
29 |
30 | .text {
31 | transform: translateY(1px);
32 | }
33 |
34 | transition: all .15s;
35 |
36 | &:hover {
37 | color: material-color('teal', '500');
38 | }
39 | &:active, &.active {
40 | .check-box {
41 | .blank {
42 | display: none;
43 | }
44 | .checked {
45 | display: initial;
46 | }
47 | }
48 | }
49 |
50 | &.active {
51 | color: material-color('teal', '500');
52 | font-weight: 700;
53 |
54 | .check-box {
55 | animation-duration: .2s;
56 | animation-fill-mode: both;
57 | animation-timing-function: ease-in-out;
58 | animation-name: pop;
59 | }
60 | }
61 | }
62 |
63 | .option + .option {
64 | margin-top: 1rem;
65 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/RegisterPage/RegisterPage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | PageTemplate,
4 | RegisterTemplate,
5 | PolyBackground,
6 | Paper
7 | } from 'components';
8 | import {HeaderContainer, RegisterFormContainer} from 'containers';
9 | import styles from './RegisterPage.scss';
10 | import classNames from 'classnames/bind';
11 | import { Helmet } from 'react-helmet';
12 |
13 | const cx = classNames.bind(styles);
14 |
15 | class RegisterPage extends Component {
16 | state = {
17 | half: false
18 | }
19 |
20 | componentDidMount() {
21 | setTimeout(() => {
22 | this.setState({
23 | half: true
24 | });
25 | })
26 | }
27 |
28 | render() {
29 | const { half } = this.state;
30 |
31 | return (
32 | }>
34 |
35 |
36 | 회원가입 :: Bitimulate
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
49 | export default RegisterPage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/WalletMenu/WalletMenu.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 | .wallet-menu {
3 | position: relative;
4 | a {
5 | display: block;
6 | padding: 1rem;
7 | font-weight: 200;
8 | border-right: 8px solid transparent;
9 | transition: .15s ease-in;
10 | &.active {
11 | font-weight: 400;
12 | color: material-color('cyan', '600');
13 | border-right: 8px solid material-color('cyan', '600');
14 | }
15 | }
16 |
17 | a + a {
18 | border-top: 1px solid material-color('grey', '300');
19 | }
20 |
21 | @include media(" Object.assign({}, data, {
29 | date: data.date * 1000,
30 | name,
31 | period
32 | }));
33 | return this.create(converted);
34 | };
35 |
36 | ChartData.statics.findByNameAndPeriod = function(name, period) {
37 | const weekly = {
38 | date: {
39 | '$lt': new Date() - 1000 * 60 * 60 * 24 * 7
40 | }
41 | };
42 |
43 | const query = Object.assign({
44 | name, period
45 | }, period === 300 ? weekly : { });
46 |
47 | return this.find(query).sort({
48 | date: 1
49 | });
50 | };
51 |
52 | module.exports = mongoose.model('ChartData', ChartData);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/PolyBackground/PolyBackground.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './PolyBackground.scss';
3 | import classNames from 'classnames/bind';
4 | import background from 'static/images/background.png';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | class PolyBackground extends Component {
9 |
10 | state = {
11 | loaded: false
12 | }
13 |
14 | componentWillMount() {
15 | const image = new Image();
16 | image.src = background;
17 |
18 | const cached = image.complete || (image.width+image.height) > 0;
19 | if(cached) {
20 | this.setState({
21 | loaded: true
22 | })
23 | return;
24 | }
25 |
26 | image.onload = () => {
27 | this.setState({
28 | loaded: true
29 | })
30 | };
31 | }
32 | componentDidMount() {
33 |
34 |
35 | }
36 |
37 | render() {
38 | const { loaded } = this.state;
39 | const { children, fixed, half, home } = this.props;
40 |
41 | return (
42 |
45 |
46 |
47 |
48 | {children}
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default PolyBackground;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/RankingContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import {bindActionCreators} from 'redux';
4 | import { Ranking, SpinnerBlock } from 'components';
5 | import * as rankingActions from 'store/modules/ranking';
6 |
7 |
8 | class RankingContainer extends Component {
9 |
10 | componentDidMount() {
11 | const { RankingActions, type } = this.props;
12 | RankingActions.getTopRanking(type);
13 | }
14 |
15 | componentWillReceiveProps(nextProps) {
16 | if(nextProps.type !== this.props.type) {
17 | const { RankingActions } = this.props;
18 | RankingActions.getTopRanking(nextProps.type);
19 | }
20 | }
21 |
22 |
23 | render() {
24 | const { ranking, count, loading, type, me } = this.props;
25 |
26 | if(ranking.isEmpty() || loading) return ;
27 |
28 | return (
29 |
30 | )
31 | }
32 | }
33 |
34 | export default connect(
35 | (state) => ({
36 | me: state.ranking.get('me'),
37 | count: state.ranking.get('count'),
38 | ranking: state.ranking.get('ranking'),
39 | loading: state.pender.pending['ranking/GET_TOP_RANKING']
40 | }),
41 | (dispatch) => ({
42 | RankingActions: bindActionCreators(rankingActions, dispatch)
43 | })
44 | )(RankingContainer);
--------------------------------------------------------------------------------
/bitimulate-backend/src/db/models/ExchangeRate.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | require('mongoose-double')(mongoose);
3 |
4 | const { Schema } = mongoose;
5 | const { Types } = Schema;
6 |
7 | const ExchangeRate = new Schema({
8 | name: String,
9 | last: Types.Double,
10 | lowestAsk: Types.Double,
11 | highestBid: Types.Double,
12 | percentChange: Types.Double,
13 | baseVolume: Types.Double,
14 | quoteVolume: Types.Double,
15 | isFrozen: Types.Double,
16 | high24hr: Types.Double,
17 | low24hr: Types.Double,
18 | lastUpdated: {
19 | type: Date,
20 | default: new Date()
21 | }
22 | });
23 |
24 | ExchangeRate.index({name: 1}, {name: 'rateTypeIdentifier', unique: true});
25 |
26 | // only for temporary use
27 | ExchangeRate.statics.drop = function () {
28 | return this.remove({}).exec();
29 | };
30 |
31 | ExchangeRate.statics.updateTicker = function(name, data) {
32 | return this.findOneAndUpdate({name}, {
33 | ...data,
34 | lastUpdated: new Date()
35 | }, { upsert: false, new: true }).exec();
36 | };
37 |
38 | ExchangeRate.statics.showAll = function() {
39 | return this.find({});
40 | };
41 |
42 | ExchangeRate.statics.getUSDRate = function() {
43 | return this.findOne({name: 'USDT_BTC'}).exec().then(
44 | (rate) => {
45 | return 1 / rate.last.value;
46 | }
47 | );
48 | };
49 |
50 | module.exports = mongoose.model('ExchangeRate', ExchangeRate);
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/WalletPage/WalletPage.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .wallet-page {
4 | padding-top: 3rem;
5 | display: flex;
6 | flex-direction: row;
7 |
8 | .ad-area-mobile {
9 | width: 300px;
10 | }
11 | @include media(" {
2 | const aggregated = [];
3 | const walletData = wallet.toJS();
4 | for(let currency in walletData) {
5 | aggregated.push({
6 | valueOnOrder: walletOnOrder.get(currency),
7 | currency,
8 | value: wallet.get(currency) + (walletOnOrder.get(currency) || 0)
9 | })
10 | }
11 | return aggregated;
12 | }
13 |
14 | export const getCorrespondingRate = (aggregated, rate) => {
15 |
16 |
17 | aggregated.forEach(
18 | w => {
19 | if(w.currency === 'BTC') {
20 | const btcRate = rate.find(r => r.get('currencyKey') === 'BTC');
21 | w.currencyName = 'Bitcoin';
22 | w.last = 1;
23 | w.percentChange = btcRate && btcRate.get('percentChange');
24 | return;
25 | }
26 | if(w.currency === 'USD') {
27 |
28 | const btcRate = rate.find(r => r.get('currencyKey') === 'BTC');
29 | if(!btcRate) return w;
30 | w.currencyName = 'Dollar';
31 | w.last = 1 / btcRate.get('last');
32 | return;
33 | }
34 |
35 | const info = rate.find(r => r.get('currencyKey') === w.currency);
36 | if(!info) return w;
37 | w.last = info.get('last');
38 | w.currencyName = info.get('currencyName');
39 | w.percentChange = info.get('percentChange');
40 | }
41 | );
42 | return aggregated;
43 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/SocialLoginButton/SocialLoginButton.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .social-login-button {
4 | display: flex;
5 | flex-direction: row;
6 | height: 2.4rem;
7 | & > div {
8 | transition: all .1s ease-in;
9 | cursor: pointer;
10 | border-radius: 2px;
11 | flex: 1;
12 | color: white;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | font-size: 1.25rem;
17 | &.facebook {
18 | border: 1px solid material-color('blue', '600');
19 | color: material-color('blue', '600');
20 | &:hover {
21 | color: white;
22 | background: material-color('blue', '500');
23 | border: 1px solid material-color('blue', '500');
24 | }
25 | &:active {
26 | border: 1px solid material-color('blue', '700');
27 | background: material-color('blue', '700');
28 | }
29 | }
30 | &.google {
31 | border: 1px solid material-color('red', '600');
32 | color: material-color('red', '600');
33 | &:hover {
34 | color: white;
35 | background: material-color('red', '500');
36 | border: 1px solid material-color('red', '500');
37 | }
38 | &:active {
39 | border: 1px solid material-color('red', '700');
40 | background: material-color('red', '700');
41 | }
42 | }
43 | }
44 |
45 | div + div {
46 | margin-left: 0.5rem;
47 | }
48 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/UserMenu/UserMenu.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | @keyframes enter {
4 | 0% {
5 | opacity: 0;
6 | transform: translateY(-16px);
7 | }
8 | 100% {
9 | opacity: 1;
10 | transform: translateY(0px);
11 | }
12 | }
13 |
14 | @keyframes leave {
15 | 0% {
16 | opacity: 1;
17 | transform: translateY(0px);
18 | }
19 | 100% {
20 | opacity: 0;
21 | transform: translateY(-16px);
22 | }
23 | }
24 |
25 | .enter {
26 | animation-duration: .2s;
27 | animation-fill-mode: both;
28 | animation-timing-function: ease-in-out;
29 | animation-name: enter;
30 | }
31 |
32 | .leave {
33 | animation-duration: .2s;
34 | animation-fill-mode: both;
35 | animation-timing-function: ease-in-out;
36 | animation-name: leave;
37 | }
38 | .user-menu {
39 | position: absolute;
40 | right: 1rem;
41 | top: 4rem;
42 | z-index: z-index-for('screen-mask');
43 | .card {
44 | @include material-shadow(2, 0.5);
45 | .menu-item {
46 | display: block;
47 | padding: 1rem;
48 | padding-top: 0.5rem;
49 | padding-bottom: 0.5rem;
50 | font-weight: 600;
51 | color: material-color('grey', '600');
52 | cursor: pointer;
53 | &:hover {
54 | background: rgb(245, 245, 245);
55 | color: material-color('teal', '600');
56 | }
57 | &:active {
58 | background: rgb(240, 240, 240);
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/atoms/Selector/Selector.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .selector-wrapper {
4 | position: relative;
5 | .selector {
6 | padding: 0.5rem;
7 | min-width: 7rem;
8 | background: rgba(255,255,255, 0.9);
9 | font-size: 0.9rem;
10 | font-weight: 600;
11 | display: flex;
12 | align-items: center;
13 | border: 1px solid material-color('grey', '400');
14 | cursor: pointer;
15 | &:hover {
16 | background: white;
17 | border: 1px solid material-color('grey', '500');
18 | }
19 | svg {
20 | margin-left: auto;
21 | }
22 | }
23 | .options-enter {
24 | animation-duration: .1s;
25 | animation-fill-mode: both;
26 | animation-timing-function: ease-in;
27 | animation-name: popFadeIn;
28 | }
29 |
30 | .options-leave {
31 | animation-duration: .1s;
32 | animation-fill-mode: both;
33 | animation-timing-function: ease-in;
34 | animation-name: popFadeOut;
35 | }
36 |
37 | .options {
38 | width: 120%;
39 | right: 0px;
40 | position: absolute;
41 | background: white;
42 | z-index: 5;
43 | @include media(" {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | { !isRegister &&
26 |
27 |
28 | {
29 | user ? (
30 |
31 | ) : (
32 |
38 | )
39 | }
40 |
41 |
}
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Header;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/WalletPage/WalletPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './WalletPage.scss';
3 | import classNames from 'classnames/bind';
4 | import { PageTemplate, WalletMenu, Card, WalletSubpage, WalletHistorySubpage, WalletProfitSubpage, ResponsiveAd } from 'components';
5 | import { HeaderContainer, SocketSubscriber } from 'containers';
6 | import { Route } from 'react-router-dom';
7 | import { Helmet } from 'react-helmet';
8 |
9 | const cx = classNames.bind(styles);
10 |
11 | const WalletPage = () => {
12 | return (
13 | } padding responsive mobileNoPadding>
14 |
15 | 내 지갑 :: Bitimulate
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default WalletPage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/TradeIndex/TradeIndex.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './TradeIndex.scss';
3 | import classNames from 'classnames/bind';
4 | import { RateInfoCard, BitcoinInfoCard } from 'components';
5 |
6 | const cx = classNames.bind(styles);
7 |
8 | const TradeIndex = ({rate, pinMap, onTogglePin, showPinned, krwRate}) => {
9 | let filtered = showPinned ? (
10 | rate.filter((info) => pinMap[info.get('currencyKey')])
11 | ) : rate;
12 | const btcInfo = rate.find(info => info.get('currencyName') === 'Bitcoin');
13 |
14 |
15 | const rateInfoCardList = filtered.map(
16 | (info) => (
17 | onTogglePin(info.get('currencyKey'))}
25 | pinned={pinMap[info.get('currencyKey')]}
26 | info={info}
27 | />
28 | )
29 | )
30 | return (
31 |
32 |
33 | { btcInfo && (
34 |
40 | ) }
41 | {rateInfoCardList}
42 |
43 |
44 | );
45 | };
46 |
47 | export default TradeIndex;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/organisms/Sidebar/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Sidebar.scss';
3 | import classNames from 'classnames/bind';
4 | import { SidebarWrapper, Button } from 'components';
5 | import { Link } from 'react-router-dom';
6 | const cx = classNames.bind(styles);
7 |
8 | const MenuItem = ({ to, children, onClick}) => {
9 | return ({children})
10 | }
11 |
12 | const Sidebar = ({
13 | visible,
14 | user,
15 | onLoginClick,
16 | onClose,
17 | onLogout
18 | }) => (
19 |
20 |
21 | {
22 | user ? [
23 |
24 | {user.get('displayName')}님,
안녕하세요!
25 |
,
26 |
27 | ]
28 | : [
29 |
30 | 모의 거래를 지금 시작해보세요!
31 |
,
32 |
35 | ]
36 | }
37 |
38 |
39 |
40 | { user && }
41 |
42 |
43 |
44 | );
45 |
46 | export default Sidebar;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/TradeDetailSubpage/TradeDetailSubpage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styles from './TradeDetailSubpage.scss';
3 | import classNames from 'classnames/bind';
4 | import { TradeChartContainer, OrderBookContainer, TradeSectionContainer, TradeHistoryContainer } from 'containers';
5 | import { Helmet } from 'react-helmet';
6 | import { ResponsiveAd } from 'components';
7 |
8 | const cx = classNames.bind(styles);
9 |
10 | class TradeDetailSubpage extends Component {
11 | scrollToTop = () => {
12 | document.documentElement.scrollTop = 0;
13 | }
14 | componentDidMount() {
15 | this.scrollToTop();
16 | }
17 | componentDidUpdate(prevProps, prevState) {
18 | if(prevProps.match.params.currencyKey !== this.props.match.params.currencyKey) {
19 | this.scrollToTop();
20 | }
21 | }
22 |
23 |
24 | render() {
25 | const { currencyKey } = this.props.match.params;
26 |
27 | return (
28 |
29 |
30 |
31 | {`[${currencyKey}] 거래소 :: Bitimulate`}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 |
48 | export default TradeDetailSubpage;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/Msgbox/Msgbox.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .msgbox {
4 | @include material-shadow(2, 0.75);
5 | width: 300px;
6 | .head {
7 | color: white;
8 | padding-top: 0.5rem;
9 | padding-bottom: 0.5rem;
10 | font-size: 2rem;
11 | text-align: center;
12 | &.success {
13 | background: material-color('green', '400');
14 | }
15 | &.error {
16 | background: material-color('red', '400');
17 | }
18 | }
19 | .content {
20 | background: white;
21 | padding: 1.50rem;
22 | font-size: 1.15rem;
23 | min-height: 6rem;
24 | font-weight: 600;
25 | display: flex;
26 | align-items: center;
27 | white-space: pre;
28 | @include media(" {
38 | return (
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 | );
56 | };
57 |
58 | export default TradeIndexOptions;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { HomePage, TradePage, RegisterPage, WalletPage, RankingPage, ReportPage, RewardPage, TermsPage } from 'components';
4 | import { Route } from 'react-router-dom';
5 | import {
6 | ScreenMaskContainer,
7 | LoginModalContainer,
8 | UserLoader,
9 | Core
10 | } from 'containers';
11 | import { Helmet } from 'react-helmet';
12 | import ReactGA from 'react-ga';
13 |
14 | function logPageView() {
15 | ReactGA.set({ page: window.location.pathname + window.location.search });
16 | ReactGA.pageview(window.location.pathname + window.location.search);
17 | }
18 |
19 | class App extends Component {
20 | componentWillReceiveProps(nextProps) {
21 | if(nextProps.location !== this.props.location) {
22 | logPageView();
23 | }
24 | }
25 |
26 | render() {
27 | return (
28 |
29 |
30 | Bitimulate - 가상화폐 모의 거래소
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
49 | export default App;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/CoinBlock/CoinBlock.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .coin-block-wrapper {
4 | width: 25%;
5 | padding: 0.5rem;
6 | @include media(" {
15 | // this.setState({
16 | // show: false
17 | // });
18 |
19 | // setTimeout(() => {
20 | // this.setState({
21 | // show: true
22 | // });
23 | // }, 0);
24 | // }, 1000)
25 |
26 | componentDidUpdate(prevProps, prevState) {
27 | if(this.state.show && prevState.show !== this.state.show) {
28 | this.loadAds();
29 | }
30 | }
31 |
32 | componentDidCatch(error, info) {
33 | // Display fallback UI
34 | this.setState({ show: false });
35 | }
36 |
37 |
38 | loadAds = () => {
39 | try {
40 | const adsbygoogle = window.adsbygoogle || [];
41 | adsbygoogle.push({});
42 | } catch (e) {
43 |
44 | }
45 | }
46 |
47 | componentDidMount() {
48 | this.loadAds();
49 | window.addEventListener('resize', this.handleResize);
50 | }
51 |
52 | componentWillUnmount() {
53 | window.removeEventListener('resize', this.handleResize);
54 | }
55 |
56 | render() {
57 | const { show } = this.state;
58 | return null;
59 | if(!show) return null;
60 | return (
61 |
62 | );
63 | }
64 | }
65 |
66 | export default ResponsiveAd;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/poloniex/currencyPairMap.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '7': 'BTC_BCN',
3 | '8': 'BTC_BELA',
4 | '10': 'BTC_BLK',
5 | '12': 'BTC_BTCD',
6 | '13': 'BTC_BTM',
7 | '14': 'BTC_BTS',
8 | '15': 'BTC_BURST',
9 | '20': 'BTC_CLAM',
10 | '24': 'BTC_DASH',
11 | '25': 'BTC_DGB',
12 | '27': 'BTC_DOGE',
13 | '28': 'BTC_EMC2',
14 | '31': 'BTC_FLDC',
15 | '32': 'BTC_FLO',
16 | '38': 'BTC_GAME',
17 | '40': 'BTC_GRC',
18 | '43': 'BTC_HUC',
19 | '50': 'BTC_LTC',
20 | '51': 'BTC_MAID',
21 | '58': 'BTC_OMNI',
22 | '61': 'BTC_NAV',
23 | '63': 'BTC_NEOS',
24 | '64': 'BTC_NMC',
25 | '69': 'BTC_NXT',
26 | '73': 'BTC_PINK',
27 | '74': 'BTC_POT',
28 | '75': 'BTC_PPC',
29 | '83': 'BTC_RIC',
30 | '89': 'BTC_STR',
31 | '92': 'BTC_SYS',
32 | '97': 'BTC_VIA',
33 | '98': 'BTC_XVC',
34 | '99': 'BTC_VRC',
35 | '100': 'BTC_VTC',
36 | '104': 'BTC_XBC',
37 | '108': 'BTC_XCP',
38 | '112': 'BTC_XEM',
39 | '114': 'BTC_XMR',
40 | '116': 'BTC_XPM',
41 | '117': 'BTC_XRP',
42 | '121': 'USDT_BTC',
43 | '131': 'XMR_BTCD',
44 | '148': 'BTC_ETH',
45 | '150': 'BTC_SC',
46 | '151': 'BTC_BCY',
47 | '153': 'BTC_EXP',
48 | '155': 'BTC_FCT',
49 | '158': 'BTC_RADS',
50 | '160': 'BTC_AMP',
51 | '162': 'BTC_DCR',
52 | '163': 'BTC_LSK',
53 | '167': 'BTC_LBC',
54 | '168': 'BTC_STEEM',
55 | '170': 'BTC_SBD',
56 | '171': 'BTC_ETC',
57 | '174': 'BTC_REP',
58 | '177': 'BTC_ARDR',
59 | '178': 'BTC_ZEC',
60 | '182': 'BTC_STRAT',
61 | '183': 'BTC_NXC',
62 | '184': 'BTC_PASC',
63 | '185': 'BTC_GNT',
64 | '187': 'BTC_GNO',
65 | '189': 'BTC_BCH',
66 | '192': 'BTC_ZRX',
67 | '194': 'BTC_CVC',
68 | '196': 'BTC_OMG',
69 | '198': 'BTC_GAS',
70 | '200': 'BTC_STORJ'
71 | };
--------------------------------------------------------------------------------
/bitimulate-frontend/src/containers/UserLoader.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | // import redux dependencies
3 | import { connect } from 'react-redux';
4 | import {bindActionCreators} from 'redux';
5 | import * as userActions from 'store/modules/user';
6 | import storage from 'lib/storage';
7 | import socket from 'lib/socket';
8 |
9 |
10 | class UserLoader extends Component {
11 |
12 | checkLoginStatus = async () => {
13 | const { UserActions } = this.props;
14 |
15 | const user = storage.get('__BTM_USER__');
16 |
17 | if(user) {
18 | UserActions.setUser(user);
19 | }
20 |
21 | try {
22 | await UserActions.checkLoginStatus();
23 | await UserActions.getMetaInfo();
24 | await UserActions.getWallet();
25 | if(!user || (user && user._id !== this.props.user.get('_id'))) {
26 | // if there is any change in login status, resave the user info
27 | storage.set('__BTM_USER__', this.props.user.toJS());
28 | }
29 | } catch (e) {
30 | // if there is an error, removes the data from the storage
31 | storage.remove('__BTM_USER__');
32 | return;
33 | }
34 | }
35 |
36 | componentDidUpdate(prevProps, prevState) {
37 | // recheck login status when userId changes
38 |
39 | if(!prevProps.user && this.props.user) {
40 | this.checkLoginStatus();
41 | // restart socket userchange
42 | socket.close();
43 | }
44 | }
45 |
46 |
47 | componentDidMount() {
48 | this.checkLoginStatus();
49 | }
50 |
51 | render() {
52 | return null;
53 | }
54 | }
55 |
56 | export default connect(
57 | (state) => ({
58 | user: state.user.get('user'),
59 | }),
60 | (dispatch) => ({
61 | UserActions: bindActionCreators(userActions, dispatch)
62 | })
63 | )(UserLoader);
64 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/RankingPage/RankingPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './RankingPage.scss';
3 | import classNames from 'classnames/bind';
4 | import { PageTemplate, Card, PolyBackground, ResponsiveAd } from 'components';
5 | import { HeaderContainer, RankingContainer } from 'containers';
6 | import { Helmet } from 'react-helmet';
7 | import { Link } from 'react-router-dom';
8 |
9 | const cx = classNames.bind(styles);
10 |
11 | const RankingPage = ({match}) => {
12 | const { type } = match.params;
13 |
14 | return (
15 | } padding>
16 |
17 | 랭킹 :: Bitimulate
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 수익률 랭킹
27 |
28 | 수익률은 USD를 기반으로 계산됩니다.
29 |
랭킹은 1시간마다 매겨집니다.
30 |
월 수익률 랭킹 1위에겐 상금이 주어집니다!
31 |
32 |
어뷰징이 발견될 시 롤백처리 될 수 있습니다.
33 |
34 |
35 |
36 |
39 | 월 수익률
40 |
41 |
44 | 전체 수익률
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default RankingPage;
--------------------------------------------------------------------------------
/bitimulate-backend/src/lib/poloniex/index.js:
--------------------------------------------------------------------------------
1 | const currencyPairMap = require('./currencyPairMap');
2 | const axios = require('axios');
3 |
4 | module.exports = (function () {
5 | function getChartData(currencyPair, period = 86400, start = 1420070400, retry) {
6 | return axios.get(`https://poloniex.com/public?command=returnChartData¤cyPair=${currencyPair}&start=${start}&end=9999999999&period=${period}`, { timeout: 15000 }).then(
7 | response => response.data
8 | );
9 | }
10 |
11 | function getCurrencyPairName(id) {
12 | if(id > 193) {
13 | return 'NULL_NULL';
14 | }
15 | return currencyPairMap[id.toString()];
16 | }
17 |
18 | function getTickers() {
19 | return axios.get('https://poloniex.com/public?command=returnTicker').then(
20 | response => response.data
21 | );
22 | };
23 |
24 | function convertToTickerObject(data) {
25 | const keys = [
26 | 'id',
27 | 'last',
28 | 'lowestAsk',
29 | 'highestBid',
30 | 'percentChange',
31 | 'baseVolume',
32 | 'quoteVolume',
33 | 'isFrozen',
34 | 'high24hr',
35 | 'low24hr'
36 | ];
37 | const object = {};
38 | data.forEach((value, i) => {
39 | // sets the name value
40 | if (i === 0) {
41 | object.name = getCurrencyPairName(value);
42 | return;
43 | }
44 | const key = keys[i];
45 | object[key] = value;
46 | });
47 |
48 | return object;
49 | }
50 |
51 | function getOrderBook(currencyPair, depth) {
52 | return axios.get(`https://poloniex.com/public?command=returnOrderBook¤cyPair=${currencyPair}&depth=3`)
53 | .then(response => response.data);
54 | }
55 |
56 | return {
57 | getCurrencyPairName,
58 | getTickers,
59 | convertToTickerObject,
60 | getChartData,
61 | getOrderBook
62 | };
63 | })();
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/WalletTable/WalletTable.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .wallet-table {
4 | margin-top: 1rem;
5 |
6 | .table-head {
7 | display: flex;
8 | flex-direction: row;
9 | height: 2.5rem;
10 | align-items: center;
11 | border-bottom: 1px solid material-color('grey', '300');
12 | .col {
13 | line-height: 1rem;
14 | font-size: 1rem;
15 | font-weight: 700;
16 | color: material-color('grey', '800');
17 | }
18 | }
19 | .col {
20 | padding: 0.5rem;
21 | &.coin {
22 | flex: 4;
23 | }
24 | &.percent {
25 | font-size: 0.8rem;
26 | text-align: right;
27 | flex: 4.5;
28 | @include media("=medium") {
55 | &:first-child {
56 | padding-left: 0;
57 | }
58 | &:last-child {
59 | padding-right: 0;
60 | }
61 | }
62 | }
63 |
64 | .info-box + .info-box {
65 | @include media(">=medium") {
66 | border-left: 1px solid material-color('grey', '300');
67 | }
68 | @include media("=medium") {
13 | display: none;
14 | }
15 | }
16 |
17 | // Settings
18 | // ==================================================
19 | $hamburger-padding-x : 0px !default;
20 | $hamburger-padding-y : 0px !default;
21 | $hamburger-layer-width : 30px !default;
22 | $hamburger-layer-height : 4px !default;
23 | $hamburger-layer-spacing : 5px !default;
24 | $hamburger-layer-color : white !default;
25 | $hamburger-layer-border-radius : 1px !default;
26 | $hamburger-hover-opacity : 0.7 !default;
27 | $hamburger-hover-transition-duration : 0.15s !default;
28 | $hamburger-hover-transition-timing-function: linear !default;
29 |
30 | // To use CSS filters as the hover effect instead of opacity,
31 | // set $hamburger-hover-use-filter as true and
32 | // change the value of $hamburger-hover-filter accordingly.
33 | $hamburger-hover-use-filter: false !default;
34 | $hamburger-hover-filter : opacity(50%) !default;
35 |
36 | // Types (Remove or comment out what you don’t need)
37 | // ==================================================
38 | $hamburger-types: (
39 | elastic,
40 | ) !default;
41 |
42 | :global {
43 | // Base Hamburger (We need this)
44 | // ==================================================
45 | @import "~hamburgers/_sass/hamburgers/base";
46 |
47 | // Hamburger types
48 | // ==================================================
49 | @import "~hamburgers/_sass/hamburgers/types/elastic";
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/molecules/CurrentInfo/CurrentInfo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './CurrentInfo.scss';
3 | import classNames from 'classnames/bind';
4 | import { LabelBlock } from 'components';
5 | import moment from 'moment/moment';
6 | const cx = classNames.bind(styles);
7 |
8 | const CurrentInfo = ({info}) => {
9 | const {
10 | lastUpdate,
11 | last,
12 | low24hr,
13 | high24hr,
14 | highestBid,
15 | lowestAsk,
16 | baseVolume,
17 | percentChange
18 | } = info.toJS();
19 |
20 |
21 | function limitDigit(value, d = 10) {
22 | const digits = (d - Math.round(Math.log10(value)));
23 | const fixed = value.toFixed(digits > d ? d : digits);
24 | const float = parseFloat(fixed)
25 | if(float > 1000) {
26 | return float.toLocaleString();
27 | }
28 | return fixed;
29 | }
30 |
31 | return (
32 |
33 |
34 |
35 | {moment(lastUpdate).format('YYYY MMM DD HH:mm')}
36 |
37 |
38 | {limitDigit(baseVolume)}
39 |
40 |
41 | {limitDigit(last)}
42 |
43 |
44 | {limitDigit(low24hr)}
45 |
46 |
47 | {limitDigit(high24hr)}
48 |
49 |
50 | {limitDigit(lowestAsk)}
51 |
52 |
53 | {limitDigit(highestBid)}
54 |
55 |
56 | {percentChange < 0 ? '' : '+' }{Math.round(percentChange * 10000
57 | ) / 100}%
58 |
59 |
60 | );
61 | };
62 |
63 | export default CurrentInfo;
--------------------------------------------------------------------------------
/bitimulate-frontend/src/components/pages/RankingPage/RankingPage.scss:
--------------------------------------------------------------------------------
1 | @import 'utils';
2 |
3 | .block {
4 | background: linear-gradient(to right, #276caf, #30a1c1);
5 | height: 25rem;
6 | z-index: 1;
7 | @include media("