diff --git a/.travis/deploy.sh b/.travis/deploy.sh index ff6ebb83..64004c0f 100755 --- a/.travis/deploy.sh +++ b/.travis/deploy.sh @@ -66,7 +66,7 @@ git ls-remote docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}" # This is the list of Docker images to build. -export DOCKER_IMAGES="vehicle-manufacture-car-builder vehicle-manufacture-manufacturing vehicle-manufacture-vda" +export DOCKER_IMAGES="vehicle-manufacture-car-builder vehicle-manufacture-manufacturing vehicle-manufacture-vda letters-of-credit" # Push the code to npm. if [ -z "${TRAVIS_TAG}" ]; then diff --git a/packages/letters-of-credit/.gitignore b/packages/letters-of-credit/.gitignore new file mode 100644 index 00000000..ffa31b34 --- /dev/null +++ b/packages/letters-of-credit/.gitignore @@ -0,0 +1,70 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# production folder +dist/ + +# composer stuff +*.card + +# install stuff +fabric-tools +loc-stage +.loc-card-store +letters-of-credit-network.bna \ No newline at end of file diff --git a/packages/letters-of-credit/Dockerfile b/packages/letters-of-credit/Dockerfile new file mode 100644 index 00000000..05eab0f8 --- /dev/null +++ b/packages/letters-of-credit/Dockerfile @@ -0,0 +1,14 @@ +FROM node:8-alpine +ENV NPM_CONFIG_LOGLEVEL warn +ENV PORT 6001 +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app +COPY package.json /usr/src/app/ +RUN apk add --no-cache git && \ + npm install --production -g pm2 && \ + npm install --production && \ + npm cache clean --force && \ + apk del git +COPY . /usr/src/app/ +EXPOSE 6001 +CMD [ "pm2-docker", "npm", "--", "start" ] \ No newline at end of file diff --git a/packages/letters-of-credit/README.md b/packages/letters-of-credit/README.md new file mode 100644 index 00000000..a3560b65 --- /dev/null +++ b/packages/letters-of-credit/README.md @@ -0,0 +1,7 @@ +## Letter of Credit Sample Application + +To deploy this application, run the provided install script by using the following command: + +`./installers/install.sh` + +This will open the tutorial and the four banking tabs that make up the application, as well as Playground and the REST server. \ No newline at end of file diff --git a/packages/letters-of-credit/installer/install.sh b/packages/letters-of-credit/installer/install.sh new file mode 100755 index 00000000..b9363351 --- /dev/null +++ b/packages/letters-of-credit/installer/install.sh @@ -0,0 +1,231 @@ +#!/bin/bash +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# REMOVE EXISTING REST SERVER, PLAYGROUND ETC +docker rm -f $(docker ps -a | grep hyperledger/* | awk '{ print $1 }') + +docker pull hyperledger/composer-playground:latest +docker pull hyperledger/composer-cli:latest +docker pull hyperledger/composer-rest-server:latest +docker pull hyperledger/letters-of-credit:latest + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# GET AND SETUP FABRIC +rm -rf $DIR/fabric-tools +mkdir $DIR/fabric-tools +chmod 777 $DIR/fabric-tools +cd $DIR/fabric-tools + +curl -O https://raw.githubusercontent.com/hyperledger/composer-tools/master/packages/fabric-dev-servers/fabric-dev-servers.tar.gz +tar -xvf $DIR/fabric-tools/fabric-dev-servers.tar.gz +$DIR/fabric-tools/startFabric.sh + +cd $DIR + +# CREATE LOCATION FOR LOCAL CARD STORE +rm -rf $(pwd)/.loc-card-store +mkdir $(pwd)/.loc-card-store +chmod 777 $(pwd)/.loc-card-store + +# CREATE CONNECTION PROFILE +rm -fr $(pwd)/loc-stage +mkdir $(pwd)/loc-stage +chmod 777 $(pwd)/loc-stage +echo '{ + "name": "hlfv1", + "version": "1.0.0", + "client": { + "organization": "Org1", + "connection": { + "timeout": { + "peer": { + "endorser": "300", + "eventHub": "300", + "eventReg": "300" + }, + "orderer": "300" + } + } + }, + "orderers": { + "orderer.example.com": { + "url": "grpc://orderer.example.com:7050", + "grpcOptions": {} + } + }, + "peers": { + "peer0.org1.example.com": { + "url": "grpc://peer0.org1.example.com:7051", + "eventUrl": "grpc://peer0.org1.example.com:7053", + "grpcOptions": {}, + "endorsingPeer": true, + "chaincodeQuery": true, + "ledgerQuery": true, + "eventSource": true + } + }, + "channels": { + "composerchannel": { + "orderers": ["orderer.example.com"], + "peers": { + "peer0.org1.example.com": {} + } + } + }, + "certificateAuthorities": { + "ca.org1.example.com": { + "url": "http://ca.org1.example.com:7054", + "caName": "ca.org1.example.com" + } + }, + "organizations": { + "Org1": { + "mspid": "Org1MSP", + "peers": ["peer0.org1.example.com"], + "certificateAuthorities": ["ca.org1.example.com"] + } + }, + "x-type": "hlfv1", + "x-commitTimeout": 100 +}' > $(pwd)/loc-stage/connection.json + +# CREATE PEER ADMIN CARD AND IMPORT +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + -v $(pwd)/loc-stage:/home/composer/loc-stage \ + -v $(pwd)/fabric-tools/fabric-scripts/hlfv1/composer/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp:/home/composer/PeerAdmin \ + hyperledger/composer-cli:latest \ + card create -p loc-stage/connection.json -u PeerAdmin -r PeerAdmin -r ChannelAdmin -f /home/composer/loc-stage/PeerAdmin.card -c PeerAdmin/signcerts/Admin@org1.example.com-cert.pem -k PeerAdmin/keystore/114aab0e76bf0c78308f89efc4b8c9423e31568da0c340ca187a9b17aa9a4457_sk + +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + -v $(pwd)/loc-stage:/home/composer/loc-stage \ + -v $(pwd)/fabric-tools/fabric-scripts/hlfv1/composer/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp:/home/composer/PeerAdmin \ + hyperledger/composer-cli:latest \ + card import -f /home/composer/loc-stage/PeerAdmin.card + +# START PLAYGROUND +docker run \ + -d \ + --network composer_default \ + --name composer \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + -p 8080:8080 \ + hyperledger/composer-playground:latest + +# WAIT FOR PLAYGROUND TO WAKE UP +sleep 5 + +# GET THE BNA +ROOT=$DIR/.. +cd $ROOT +npm install +cd $DIR +cp $ROOT/node_modules/letters-of-credit-network/dist/letters-of-credit-network.bna letters-of-credit-network.bna + +# INSTALL THE BNA +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/letters-of-credit-network.bna:/home/composer/letters-of-credit-network.bna \ + -v $(pwd)/loc-stage:/home/composer/loc-stage \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + hyperledger/composer-cli:latest \ + network install -c PeerAdmin@hlfv1 -a letters-of-credit-network.bna + +NETWORK_VERSION=$(grep -o '"version": *"[^"]*"' $ROOT/node_modules/letters-of-credit-network/package.json | grep -o '[0-9]\.[0-9]\.[0-9]') + +# START THE BNA +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/letters-of-credit-network.bna:/home/composer/letters-of-credit-network.bna \ + -v $(pwd)/loc-stage:/home/composer/loc-stage \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + hyperledger/composer-cli:latest \ + network start -n letters-of-credit-network -V $NETWORK_VERSION -c PeerAdmin@hlfv1 -A admin -S adminpw -f /home/composer/loc-stage/bnaadmin.card + +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/loc-stage:/home/composer/loc-stage \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + hyperledger/composer-cli:latest \ + card import -f /home/composer/loc-stage/bnaadmin.card + +# CREATE THE NEEDED PARTICIPANTS +docker run \ + --rm \ + --network composer_default \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + hyperledger/composer-cli:latest \ + transaction submit -c admin@letters-of-credit-network -d '{"$class": "org.example.loc.CreateDemoParticipants"}' + +# SET CORRECT PERMISSIONS +docker exec \ + composer \ + find /home/composer/.composer -name "*" -exec chmod 777 {} \; + +# START THE REST SERVER +docker run \ + -d \ + --network composer_default \ + --name rest \ + -v $(pwd)/.loc-card-store:/home/composer/.composer \ + -e COMPOSER_CARD=admin@letters-of-credit-network \ + -e COMPOSER_NAMESPACES=never \ + -p 3000:3000 \ + hyperledger/composer-rest-server:latest + +#WAIT FOR REST SERVER TO WAKE UP +sleep 10 + +# START THE LOC APPLICATION +docker run \ +-d \ +--network composer_default \ +--name vda \ +-e REACT_APP_REST_SERVER_CONFIG='{"webSocketURL": "ws://localhost:3000", "httpURL": "http://localhost:3000/api"}' \ +-p 6001:6001 \ +hyperledger/letters-of-credit:latest + +#WAIT FOR REACT SERVER TO WAKE UP +sleep 10 + +# OPEN THE APPLICATION +URLS="http://localhost:6001/tutorial http://localhost:6001/alice http://localhost:6001/matias http://localhost:6001/ella http://localhost:6001/bob http://localhost:8080 http://localhost:3000/explorer/" +case "$(uname)" in +"Darwin") open ${URLS} + ;; +"Linux") if [ -n "$BROWSER" ] ; then + $BROWSER http://localhost:6001/tutorial http://localhost:6001/alice http://localhost:6001/matias http://localhost:6001/ella http://localhost:6001/bob http://localhost:8080 http://localhost:3000/explorer/ + elif which x-www-browser > /dev/null ; then + nohup x-www-browser ${URLS} < /dev/null > /dev/null 2>&1 & + elif which xdg-open > /dev/null ; then + for URL in ${URLS} ; do + xdg-open ${URL} + done + elif which gnome-open > /dev/null ; then + gnome-open http://localhost:6001/tutorial http://localhost:6001/alice http://localhost:6001/matias http://localhost:6001/ella http://localhost:6001/bob http://localhost:8080 http://localhost:3000/explorer/ + else + echo "Could not detect web browser to use - please launch Application and Composer Playground URL using your chosen browser ie: http://localhost:8080 or set your BROWSER variable to the browser launcher in your PATH" + fi + ;; +*) echo "Playground not launched - this OS is currently not supported " + ;; +esac \ No newline at end of file diff --git a/packages/letters-of-credit/package.json b/packages/letters-of-credit/package.json new file mode 100644 index 00000000..3e88f6e8 --- /dev/null +++ b/packages/letters-of-credit/package.json @@ -0,0 +1,29 @@ +{ + "name": "letters-of-credit", + "version": "0.0.7", + "private": true, + "author": "Erin Hughes, Hannah Rayner, Nikola Ignatov, Jackson Ross, Andrew Hurt", + "dependencies": { + "axios": "^0.18.0", + "letters-of-credit-network": "^0.2.4", + "react": "^16.2.0", + "react-dom": "^16.2.0", + "react-redux": "^5.0.7", + "react-router-dom": "^4.2.2", + "react-scripts": "1.1.1", + "react-stepper-horizontal": "^1.0.10", + "react-toggle": "^4.0.2", + "redux": "^3.7.2" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject", + "deployNetwork": "composer runtime install --card PeerAdmin@hlfv1 --businessNetworkName letters-of-credit-network && composer network start --card PeerAdmin@hlfv1 --networkAdmin admin --networkAdminEnrollSecret adminpw --archiveFile *.bna --file networkadmin.card && composer-rest-server -c admin@letters-of-credit-network -n never -w true && composer-rest-server -c admin@letters-of-credit-network -n never -w true" + }, + "devDependencies": { + "gulp": "^3.9.1", + "gulp-sass": "^3.2.1" + } +} diff --git a/packages/letters-of-credit/public/favicon.ico b/packages/letters-of-credit/public/favicon.ico new file mode 100644 index 00000000..4fcebaee Binary files /dev/null and b/packages/letters-of-credit/public/favicon.ico differ diff --git a/packages/letters-of-credit/public/index.html b/packages/letters-of-credit/public/index.html new file mode 100644 index 00000000..438d694e --- /dev/null +++ b/packages/letters-of-credit/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + Letters of Credit + + + +
+ + + diff --git a/packages/letters-of-credit/public/manifest.json b/packages/letters-of-credit/public/manifest.json new file mode 100644 index 00000000..ef19ec24 --- /dev/null +++ b/packages/letters-of-credit/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/packages/letters-of-credit/src/App.css b/packages/letters-of-credit/src/App.css new file mode 100644 index 00000000..40a1fc2f --- /dev/null +++ b/packages/letters-of-credit/src/App.css @@ -0,0 +1,7 @@ +.App { + text-align: center; +} + +button:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/App.js b/packages/letters-of-credit/src/App.js new file mode 100644 index 00000000..a8a27b73 --- /dev/null +++ b/packages/letters-of-credit/src/App.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import { Switch, Route, Redirect } from 'react-router-dom'; +import './App.css'; +import LetterOfCredit from './components/LetterOfCredit/LetterOfCredit.js'; +import AlicePage from './components/Pages/AlicePage/AlicePage.js'; +import BobPage from './components/Pages/BobPage/BobPage.js'; +import MatiasPage from './components/Pages/MatiasPage/MatiasPage.js'; +import EllaPage from './components/Pages/EllaPage/EllaPage.js'; +import TutorialPage from './components/Pages/TutorialPage/TutorialPage.js'; + +class App extends Component { + constructor() { + super(); + this.state = { + currentLetter: {}, + currentUser: "alice" + }; + this.changeUser = this.changeUser.bind(this); + this.goToLetterScreen = this.goToLetterScreen.bind(this); + } + + goToLetterScreen(letter, isApply) { + this.setState({ + currentLetter: letter, + isApply: isApply + }); + } + + changeUser(user) { + if(user === 'alice') { + this.setState({ + currentUser: "alice" + }); + } else if (user === 'bob') { + this.setState({ + currentUser: "bob" + }); + } else if (user === 'matias') { + this.setState({ + currentUser: "matias" + }); + } else { + this.setState({ + currentUser: "ella" + }); + } + } + + render() { + return ( + + }/> + }/> + }/> + }/> + }/> + }/> + + + ); + } +} + +export default App; diff --git a/packages/letters-of-credit/src/App.test.js b/packages/letters-of-credit/src/App.test.js new file mode 100644 index 00000000..a754b201 --- /dev/null +++ b/packages/letters-of-credit/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/packages/letters-of-credit/src/actions/actions.js b/packages/letters-of-credit/src/actions/actions.js new file mode 100644 index 00000000..d05deefa --- /dev/null +++ b/packages/letters-of-credit/src/actions/actions.js @@ -0,0 +1,32 @@ +export const GET_APPLICANT = 'GET_APPLICANT'; +export const GET_BENEFICIARY = 'GET_BENEFICIARY'; +export const GET_PRODUCT_DETAILS = 'GET_PRODUCT_DETAILS'; +export const GET_RULES = 'GET_RULES'; + +export function getApplicant(applicant) { + return { + type: GET_APPLICANT, + payload: applicant + }; +} + +export function getBeneficiary(beneficiary) { + return { + type: GET_BENEFICIARY, + payload: beneficiary + } +} + +export function getProductDeatils(productDetails) { + return { + type: GET_PRODUCT_DETAILS, + payload: productDetails + }; +} + +export function getRules(rules) { + return { + type: GET_RULES, + payload: rules + }; +} diff --git a/packages/letters-of-credit/src/components/Alert/Alert.js b/packages/letters-of-credit/src/components/Alert/Alert.js new file mode 100644 index 00000000..375fca6f --- /dev/null +++ b/packages/letters-of-credit/src/components/Alert/Alert.js @@ -0,0 +1,34 @@ +import React from 'react'; +import '../../stylesheets/css/main.css'; + +class Alert extends React.Component { + constructor(props) { + super(props); + this.state = { + amount: this.props.amount, + show: this.props.show + }; + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.setState({ + show: false + }); + } + + render() { + let containerStyle = (this.state.show) ? "alertContainer" : "alertContainer invisible"; + return ( +
+
+
+

Your balance has increased by {"$" + this.state.amount}.

+ +
+
+ ); + } +} + +export default Alert; diff --git a/packages/letters-of-credit/src/components/Block/Block.js b/packages/letters-of-credit/src/components/Block/Block.js new file mode 100644 index 00000000..c56f1c05 --- /dev/null +++ b/packages/letters-of-credit/src/components/Block/Block.js @@ -0,0 +1,29 @@ +import React from 'react'; +import '../../stylesheets/css/main.css'; + +class Block extends React.Component { + constructor(props) { + super(props); + this.state = { + transactionDetails: this.props.transactionDetails, + date: this.props.date, + time: this.props.time, + number: this.props.number + }; + } + + render() { + return ( +
+
+
+

{this.state.transactionDetails}

+

{this.state.date}

+

{this.state.time}

+
+
+ ); + } +} + +export default Block; diff --git a/packages/letters-of-credit/src/components/BlockChainDisplay/BlockChainDisplay.js b/packages/letters-of-credit/src/components/BlockChainDisplay/BlockChainDisplay.js new file mode 100644 index 00000000..efbd3b71 --- /dev/null +++ b/packages/letters-of-credit/src/components/BlockChainDisplay/BlockChainDisplay.js @@ -0,0 +1,83 @@ +import React from 'react'; +import '../../stylesheets/css/main.css'; +import Block from '../../components/Block/Block.js'; + +class BlockChainDisplay extends React.Component { + constructor(props) { + super(props); + this.state = { + transactions: [] + } + } + + getSpecificInfo() { + let transactions = []; + this.props.transactions.map((tx, index) => { + let transaction = { + name: "", + timestamp: tx.transactionTimestamp + } + if (tx.transactionType === 'org.example.loc.InitialApplication') { + transaction.name = 'Created by Alice'; + } else if (tx.transactionType === 'org.example.loc.Approve') { + switch (index) { + case 1: + transaction.name = 'Approved by Matías'; + break; + case 2: + transaction.name = 'Approved by Ella'; + break; + case 3: + transaction.name = 'Approved by Bob'; + break; + default: + transaction.name = 'Approved'; + break; + } + } else if (tx.transactionType === 'org.example.loc.ShipProduct') { + transaction.name = 'Shipped by Bob'; + } else if (tx.transactionType === 'org.example.loc.ReceiveProduct') { + transaction.name = 'Received by Alice'; + } else if (tx.transactionType === 'org.example.loc.ReadyForPayment') { + transaction.name = 'Paid by Matías'; + } else if (tx.transactionType === 'org.example.loc.Close') { + transaction.name = 'Closed by Ella'; + } else { + transaction.name = 'Rejected'; + } + transactions.push(transaction); + }); + return transactions; + } + + addLeadingZero(number) { + return (number < 10) ? "0" + number : number; + } + + render() { + let transactions = this.getSpecificInfo(); + let blocks = []; + if(transactions.length) { + for (let i = transactions.length; i > 0; i--) { + let name = transactions[i-1].name; + let blockNumber = this.addLeadingZero(i); + let dateTime = new Date(transactions[i-1].timestamp); + let date = dateTime.getFullYear() + '-' + this.addLeadingZero(dateTime.getMonth()+1) + '-' + this.addLeadingZero(dateTime.getDate()); + let time = dateTime.toTimeString().split(' ')[0]; + blocks.push(); + + } + } + + return ( +
+
+
+
+ {blocks} +
+ ); + } +} + +export default BlockChainDisplay; diff --git a/packages/letters-of-credit/src/components/DetailsCard/DetailsCard.js b/packages/letters-of-credit/src/components/DetailsCard/DetailsCard.js new file mode 100644 index 00000000..736f5994 --- /dev/null +++ b/packages/letters-of-credit/src/components/DetailsCard/DetailsCard.js @@ -0,0 +1,133 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; +import { connect } from "react-redux"; +import { getProductDeatils } from "../../actions/actions"; +import { getRules } from "../../actions/actions"; + +class DetailsCard extends Component { + constructor(props) { + super(props); + this.state = { + data: this.props.data, + editable: false + } + } + + switchEditable() { + const currentState = this.state.editable; + this.setState({ + editable: !currentState + }); + } + + handleChange(index, event) { + const data = this.state.data; + data[index] = ((this.props.type === "Rules") ? {ruleText: event.target.value} : event.target.value); + + this.setState({ + data: data + }); + + if(this.props.type === "Product") { + this.props.getProductDeatils({ + type: this.state.data[1], + quantity: parseInt(this.state.data[2], 10), + pricePerUnit: parseFloat(this.state.data[3], 10), + total: parseFloat(this.state.data[2]*this.state.data[3], 10) + }); + } + } + + render() { + let mainHeadingTxt = this.props.data[0]; + let jsx; + + let containerClasses = this.props.disabled ? "cardContainer disabled" : "cardContainer"; + + switch(this.props.type) { + case 'Person': + jsx = ( +
+ NAME + {this.state.data[1]} + COMPANY NAME + {this.state.data[2]} + IBAN + {this.state.data[3]} + SWIFT CODE + {this.state.data[4]} + BANK NAME + {this.state.data[5]} +
+ ); + break; + case 'Product': + let currency, amount; + if (this.props.user === 'alice' || this.props.user === 'matias') { + currency = '€'; + amount = this.state.data[3]; + } else { + currency = '$' + amount = this.state.data[3] * 1.15; + } + + jsx = ( +
+ TYPE + { (this.state.editable) ? : {this.state.data[1]} } + QUANTITY + { (this.state.editable) ? : {this.state.data[2] ? this.state.data[2] : "0"} } + PRICE PER UNIT + { (this.state.editable) ? : {currency + (this.state.data[3] ? amount.toLocaleString(undefined, {minimumFractionDigits: 2}) : "0.00")} } + TOTAL + {currency + (this.state.data[2]*amount).toLocaleString(undefined, {minimumFractionDigits: 2})} +
+ ); + break; + case 'Rules': + mainHeadingTxt = "Terms of Letter of Credit"; + if(this.state.editable) { + jsx = ( +
    + {this.state.data.map(function(rule, i) { + return
  • ; + }, this)} +
+ ); + } + else { + jsx = ( +
    + {this.state.data.map(function(rule) { + return
  • {rule.ruleText}
  • ; + })} +
+ ); + } + break; + } + + let buttonTxt = this.state.editable ? "Save" : "Edit"; + let editButtonStyle = this.state.editable ? { float: 'right' } : {}; + let styles = this.props.type === 'Rules' ? "outerDiv rules" : "outerDiv"; + + return ( +
+
+
{mainHeadingTxt}
+ {jsx} +
+ { this.props.canEdit && } +
+ ); + } +} + +const mapDispatchToProps = dispatch => { + return { + getProductDeatils: productDetails => dispatch(getProductDeatils(productDetails)), + getRules: rules => dispatch(getRules(rules)) + }; +}; + +export default connect(null, mapDispatchToProps)(DetailsCard); diff --git a/packages/letters-of-credit/src/components/LetterOfCredit/LetterOfCredit.js b/packages/letters-of-credit/src/components/LetterOfCredit/LetterOfCredit.js new file mode 100644 index 00000000..c42574dd --- /dev/null +++ b/packages/letters-of-credit/src/components/LetterOfCredit/LetterOfCredit.js @@ -0,0 +1,443 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; +import { Redirect } from 'react-router-dom'; +import DetailsCard from '../DetailsCard/DetailsCard.js'; +import BlockChainDisplay from '../BlockChainDisplay/BlockChainDisplay.js'; +import axios from 'axios'; +import { connect } from "react-redux"; +import { getProductDeatils } from "../../actions/actions"; +import Config from '../../utils/config'; +import backButtonIcon from '../../resources/images/left-arrow.svg' +import Stepper from '../../components/Stepper/Stepper.js'; +import Modal from '../../components/Modal/Modal.js' + +class LetterOfCredit extends Component { + constructor(props) { + super(props); + + let letter = {}; + let isApply = this.props.isApply; + + // check if there's a letter stored in local storage, meaning page has been refreshed + if(localStorage.getItem('letter')) { + letter = JSON.parse(localStorage.getItem('letter')); + } + else { + // if nothing has been stored then letter is being opened for the first time - use props + letter = this.props.letter; + // store that in local storage in case of a refresh + // can only store strings so need to stringify letter object + localStorage.setItem('letter', JSON.stringify(this.props.letter)); + } + + // check if letter is an empty object and if it is, manually set isApply to true + // in order to handle a refresh of the create page + if(Object.keys(letter).length === 0 && letter.constructor === Object) { + isApply = true; + } + + this.state = { + isApply: isApply, + letter: letter, + user: this.props.match.params.name, + transactions: [], + disableButtons: false, + redirect: false, + redirectTo: '', + showModal: false + } + + this.showModal = this.showModal.bind(this); + this.hideModal = this.hideModal.bind(this); + this.handleOnClick = this.handleOnClick.bind(this); + this.config = new Config(); + } + + handleOnClick(user) { + this.props.callback(user); + this.setState({redirect: true, redirectTo: user}); + } + + componentWillMount() { + axios.get(this.config.restServer.httpURL+'/system/historian') + .then((response) => { + let relevantTransactions = []; + let transactionTypes = ["InitialApplication", "Approve", "Reject", "ShipProduct", "ReceiveProduct", "ReadyForPayment", "Close"]; + response.data.forEach((i) => { + let transactionLetter = ((i.eventsEmitted.length) ? decodeURIComponent(i.eventsEmitted[0].loc.split("#")[1]) : undefined); + let longName = i.transactionType.split(".") + let name = longName[longName.length - 1]; + + if(transactionTypes.includes(name) && this.state.letter.letterId === transactionLetter) { + relevantTransactions.push(i); + } + }); + relevantTransactions.sort((a,b) => { + // creating a new date object to account for daylight savings + a.transactionTimestamp = new Date(a.transactionTimestamp); + b.transactionTimestamp = new Date(b.transactionTimestamp); + return a.transactionTimestamp - b.transactionTimestamp; + }); + + this.setState ({ + transactions: relevantTransactions + }); + }) + .catch(error => { + console.log(error); + }); + } + + componentDidMount() { + let user = this.props.match.params.name; + let id = this.props.match.params.id; + document.title = (user === "matias" ? "Matías" : user.charAt(0).toUpperCase() + user.substr(1)) + " - " + (id === "create" ? "Create LoC" : id); + } + + componentWillUnmount() { + // clear local storage when moving away from component + localStorage.clear(); + } + + showModal(tx) { + // work out what transaction will be made if the yes button is clicked + const txTypes = { + CREATE: "CREATE", + APPROVE: "APPROVE", + REJECT: "REJECT", + PAY: "PAY", + CLOSE: "CLOSE" + } + + let callback; + if (tx === 'CREATE') { + callback = () => { + this.hideModal(); + this.createLOC(this.props.productDetails.type, this.props.productDetails.quantity, this.props.productDetails.pricePerUnit, this.props.rules) + }; + } else if (tx === txTypes.APPROVE) { + callback = () => { + this.hideModal(); + this.approveLOC(this.state.letter.letterId, this.state.user) + }; + } else if (tx === txTypes.REJECT) { + callback = () => { + this.hideModal(); + this.rejectLOC(this.state.letter.letterId) + } + } else if (tx === txTypes.PAY) { + callback = () => { + this.hideModal(); + this.payLOC(this.state.letter.letterId) + } + } else { + callback = () => { + this.hideModal(); + this.closeLOC(this.state.letter.letterId) + } + } + + this.setState({ + showModal: true, + modalType: tx, + modalFunction: callback + }); + } + + hideModal() { + this.setState({ + showModal: false + }); + } + + createRules() { + let rules = []; + let ruleIndex = 1; + this.props.rules.map((i) => { + if (i.ruleText !== "") { + rules.push({ + "$class": "org.example.loc.Rule", + "ruleId": "rule"+ruleIndex, + "ruleText": i.ruleText + }); + ruleIndex++; + } + }); + return rules; + } + + createLOC(type, quantity, price, rules) { + this.setState({ + disableButtons: true + }); + let currentTime = new Date().toLocaleTimeString().split(":").join(''); + axios.post(this.config.restServer.httpURL+'/InitialApplication', { + "$class": "org.example.loc.InitialApplication", + "letterId": ("L" + currentTime), + "applicant": "resource:org.example.loc.Customer#alice", + "beneficiary": "resource:org.example.loc.Customer#bob", + "rules": this.createRules(), + "productDetails": { + "$class": "org.example.loc.ProductDetails", + "productType": type, + "quantity": quantity, + "pricePerUnit": price.toFixed(2), + "id": "string" + }, + "transactionId": "", + "timestamp": "2018-03-13T11:35:00.218Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .then(() => { + this.setState({ + disableButtons: false + }); + this.props.getProductDeatils({ + type: "", + quantity: 0, + pricePerUnit: 0, + total: 0 + }); + this.handleOnClick(this.state.user); + }) + .catch(error => { + console.log(error); + }); + } + + approveLOC(letterId, approvingParty) { + let resourceURL = "resource:org.example.loc.Customer#"; + + if (approvingParty === 'ella' || approvingParty === 'matias') { + resourceURL = "resource:org.example.loc.BankEmployee#"; + } + + if(!this.state.letter.approval.includes(this.state.user)) { + this.setState({ + disableButtons: true + }); + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/Approve', { + "$class": "org.example.loc.Approve", + "loc": letter, + "approvingParty": resourceURL+approvingParty, + "transactionId": "", + "timestamp": "2018-03-13T11:25:08.043Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .then(() => { + this.setState({ + disableButtons: false + }); + this.handleOnClick(this.state.user); + }) + .catch(error => { + console.log(error); + }); + } + } + + rejectLOC(letterId) { + this.setState({ + disableButtons: true + }); + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/Reject', { + "$class": "org.example.loc.Reject", + "loc": letter, + "closeReason": "Letter has been rejected", + "transactionId": "", + "timestamp": "2018-03-13T11:35:00.281Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .then(() => { + this.setState({ + disableButtons: false + }); + this.handleOnClick(this.state.user); + }) + .catch(error => { + console.log(error); + }); + } + + payLOC(letterId) { + this.setState({ + disableButtons: true + }); + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/ReadyForPayment', { + "$class" : "org.example.loc.ReadyForPayment", + "loc": letter, + 'beneficiary': "resource:org.example.loc.Customer#bob", + "transactionId": "", + "timestamp": "2018-03-13T11:35:00.281Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .then(() => { + this.setState({ + disableButtons: false + }); + this.handleOnClick(this.state.user); + }) + .catch(error => { + console.log(error); + }); + } + + closeLOC(letterId) { + this.setState({ + disableButtons: true + }); + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/Close', { + "$class": "org.example.loc.Close", + "loc": letter, + "closeReason": "Letter has been completed.", + "transactionId": "", + "timestamp": "2018-03-13T11:35:00.139Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .then(() => { + this.setState({ + disableButtons: false + }); + this.handleOnClick(this.state.user); + }) + .catch(error => { + console.log(error); + }) + } + + render() { + if (this.state.redirect) { + return ; + } + + let activeStep = 0; + if (this.state.letter.status === 'AWAITING_APPROVAL') { + if (!this.state.letter.approval.includes('resource:org.example.loc.BankEmployee#matias')) { + + activeStep = 1; + } + else if (!this.state.letter.approval.includes('resource:org.example.loc.BankEmployee#ella')) { + activeStep = 2; + } + else if (!this.state.letter.approval.includes('resource:org.example.loc.Customer#bob')) { + activeStep = 3; + } + } + else if (this.state.letter.status === 'APPROVED'){ + activeStep = 4; + } else if (this.state.letter.status === 'SHIPPED') { + activeStep = 5; + } else if (this.state.letter.status === 'RECEIVED'){ + activeStep = 6; + } else if (this.state.letter.status === 'READY_FOR_PAYMENT'){ + activeStep = 7; + } else if (this.state.letter.status === 'CLOSED'){ + activeStep = 8; + } + + let productDetails = this.props.productDetails; + let rules = this.props.rules; + let buttonJSX = (
); + if (!this.state.isApply) { + productDetails = { + type: this.state.letter.productDetails.productType, + quantity: this.state.letter.productDetails.quantity, + pricePerUnit: this.state.letter.productDetails.pricePerUnit + }; + rules = this.state.letter.rules; + let isAwaitingApproval = ( + this.state.letter.status === 'AWAITING_APPROVAL' && + (!this.state.letter.approval.includes('resource:org.example.loc.Customer#'+this.state.user) && + (!this.state.letter.approval.includes('resource:org.example.loc.BankEmployee#'+this.state.user))) + ); + if (isAwaitingApproval) { + buttonJSX = ( +
+ + +
+ ); + } else if (this.state.letter.status === 'RECEIVED' && this.state.user === 'matias') { + buttonJSX = ( +
+ +
+ ); + } else if (this.state.letter.status === 'READY_FOR_PAYMENT' && this.state.user === 'ella') { + buttonJSX = ( +
+ +
+ ); + } else { + buttonJSX = (
); + } + } else { + buttonJSX = ( +
+ +
+ ); + } + + let username = (this.state.user.charAt(3) === 'i') ? 'Matías' : this.state.user.charAt(0).toUpperCase() + this.state.user.slice(1); + if (username === 'Alice') username += ' - Applicant'; + else if (username === 'Matías') username += ' - Issuing Bank'; + else if (username === 'Ella') username += ' - Exporting Bank'; + else username += ' - Beneficiary'; + + if (!this.state.disableButtons) { + return ( +
+ {this.hideModal()}} yesCallback={this.state.modalFunction}/> +
+
+ go back {if(!this.state.disableButtons){this.handleOnClick(this.state.user)}}}/> +
+

Letter of Credit

+

{username}

+
+ + + + + + + + + + + + + + +

Contract Details

+ {buttonJSX} + { this.state.disableButtons &&
Please wait...
} +
+ ); + } else { + return ( +
+ Please wait... +
+ ); + } + } +} + +const mapStateToProps = state => { + return { + applicant: state.getLetterInputReducer.applicant, + beneficiary: state.getLetterInputReducer.beneficiary, + productDetails: state.getLetterInputReducer.productDetails, + rules: state.getLetterInputReducer.rules + }; +}; + +const mapDispatchToProps = dispatch => { + return { + getProductDeatils: productDetails => dispatch(getProductDeatils(productDetails)) + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(LetterOfCredit); diff --git a/packages/letters-of-credit/src/components/LoCCard/LoCApplyCard.js b/packages/letters-of-credit/src/components/LoCCard/LoCApplyCard.js new file mode 100644 index 00000000..ab0d83f7 --- /dev/null +++ b/packages/letters-of-credit/src/components/LoCCard/LoCApplyCard.js @@ -0,0 +1,36 @@ +import React, {Component} from 'react'; +import { Redirect } from 'react-router-dom'; +import '../../stylesheets/css/main.css'; + +class LoCApplyCard extends Component { + constructor(props) { + super(props); + this.state = { + redirect: false + } + this.handleOnClick = this.handleOnClick.bind(this); + } + + handleOnClick() { + this.props.callback({}, true); + this.setState({redirect: true}); + } + + render() { + if (this.state.redirect) { + return ; + } + return ( +
+

{this.props.user === 'alice' ? 'Letter of Credit Application' : 'Letter of Credit Applications'}

+

A letter of credit is issued by a bank to another bank (especially one in a different country) to serve as a guarantee for payments made to a specified person under specified conditions.

+ {this.props.user === 'alice' && + + } +
+ ); + } +} + + +export default LoCApplyCard; \ No newline at end of file diff --git a/packages/letters-of-credit/src/components/LoCCard/LoCCard.js b/packages/letters-of-credit/src/components/LoCCard/LoCCard.js new file mode 100644 index 00000000..62d53c64 --- /dev/null +++ b/packages/letters-of-credit/src/components/LoCCard/LoCCard.js @@ -0,0 +1,204 @@ +import React, {Component} from 'react'; +import { Redirect } from 'react-router-dom'; +import Config from '../../utils/config'; +import '../../stylesheets/css/main.css'; +import axios from 'axios'; +import viewButtonIconAlice from '../../resources/images/viewLocIcon.png'; +import viewButtonIconBob from '../../resources/images/viewLocIconBob.png'; +import Toggle from 'react-toggle'; +import "react-toggle/style.css"; +import Modal from '../Modal/Modal.js'; +import viewArrow from '../../resources/images/right-arrow.svg' + +class LoCCard extends Component { + constructor(props) { + super(props); + this.state = { + redirect: false, + showModal: false, + toggleChecked: false, + toggleDisabled: false + } + + this.handleOnClick = this.handleOnClick.bind(this); + this.showModal = this.showModal.bind(this); + this.hideModal = this.hideModal.bind(this); + this.config = new Config(); + } + + handleOnClick() { + this.props.callback(this.props.letter, false); + this.setState({redirect: true}); + } + + showModal() { + this.setState({ + showModal: true + }); + } + + hideModal() { + this.setState({ + showModal: false + }); + } + + shipProduct(letterId, evidenceHash) { + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/ShipProduct', { + "$class": "org.example.loc.ShipProduct", + "loc": letter, + "evidence": evidenceHash, + "transactionId": "", + "timestamp": "2018-03-13T11:25:08.043Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .catch(error => { + console.log(error); + }); + this.setState({ + toggleDisabled: true + }); + } + + receiveProduct(letterId) { + let letter = "resource:org.example.loc.LetterOfCredit#" + letterId; + axios.post(this.config.restServer.httpURL+'/ReceiveProduct', { + "$class": "org.example.loc.ReceiveProduct", + "loc": letter, + "transactionId": "", + "timestamp": "2018-03-13T11:25:08.043Z" // the transactions seem to need this field filled in; when submitted the correct time will replace this value + }) + .catch(error => { + console.log(error); + }); + this.setState({ + toggleDisabled: true + }); + } + + generateStatus(letter) { + let status = ''; + if (letter.status === 'AWAITING_APPROVAL') { + status = 'Awaiting Approval'; + } else if (letter.status === 'READY_FOR_PAYMENT'){ + status = 'Payment Made'; + } + else { + status = letter.status.toUpperCase(); + } + return status.toUpperCase(); + } + + generateCardContents(letter, user) { + let contents; + let newMessage = ""; + if(!this.props.letter.approval.includes("bob")){ + newMessage = "NEW"; + } + //generate new LoC cards + if (user === 'bob') { + contents = ( +
+
+

{newMessage}

+

{'Ref: ' + letter.letterId}

+

Product Type: {letter.productDetails.productType}

+
+ + Ship Product +
+
+ View Letter of Credit this.handleOnClick()}/> +
+ ); + } + else { // if the current user is not bob then it must be alice + contents = ( +
+
+

{this.generateStatus(letter)}

+

{'Ref: ' + letter.letterId}

+

Product Type: {letter.productDetails.productType}

+
+ + Receive Product +
+ +
+
+ ); + } + let shippingText; + let checked = letter.status !== 'APPROVED'; + //generate accepted LoC cards + if (user === 'bob') { + if (letter.status !== 'AWAITING_APPROVAL') { + // generating a hash from the timestamp + let idStyle; + shippingText = "Ship Product"; + if (letter.status !== 'APPROVED'){ + idStyle = "LoCCardBobAccepted"; + this.state.toggleChecked = true; + this.state.toggleDisabled = true; + shippingText = "Product Shipped"; + } + let hash = new Date().getTime().toString(24); + contents = ( +
+ {this.shipProduct(letter.letterId, hash)}}/> +
+

{this.generateStatus(letter)}

+

{'Ref: ' + letter.letterId}

+

Product Type: {letter.productDetails.productType}

+
+ + {shippingText} +
+ View Letter of Credit +
+
+ ); + } + } else { + if (letter.status !== 'AWAITING_APPROVAL' && letter.status !== 'APPROVED') { + // generating a hash from the timestamp + shippingText = "Receive Product"; + if (letter.status !== 'SHIPPED') { + this.state.toggleChecked = true; + this.state.toggleDisabled = true; + shippingText = "Product Received"; + } + contents = ( +
+
+

{this.generateStatus(letter)}

+

{'Ref: ' + letter.letterId}

+

Product Type: {letter.productDetails.productType}

+
+ {this.receiveProduct(letter.letterId)}} disabled ={this.state.toggleDisabled}/> + {shippingText} +
+ +
+
+ ); + } + } + return contents; + } + + render() { + if (this.state.redirect) { + return ; + } + return ( + this.generateCardContents(this.props.letter, this.props.user) + ); + } +} + +export default LoCCard; diff --git a/packages/letters-of-credit/src/components/Modal/Modal.js b/packages/letters-of-credit/src/components/Modal/Modal.js new file mode 100644 index 00000000..70ddef4a --- /dev/null +++ b/packages/letters-of-credit/src/components/Modal/Modal.js @@ -0,0 +1,111 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; + +class Modal extends Component { + constructor() { + super(); + + this.state = { + isChecked: false, + isLoading: false + } + + this.handleChange = this.handleChange.bind(this); + this.cancelShipCallback = this.cancelShipCallback.bind(this); + this.uploadInvoiceCallback = this.uploadInvoiceCallback.bind(this); + } + + handleChange() { + const previousState = this.state.isChecked; + this.setState({ + isChecked: !previousState + }); + } + + cancelShipCallback() { + this.setState({ + isChecked: false + }); + this.props.cancelCallback(); + } + + uploadInvoiceCallback() { + this.setState({ + isLoading: true + }); + this.props.yesCallback(); + } + + getMessage() { + let message = ""; + if (this.props.modalType === 'CREATE' || this.props.modalType === 'APPROVE') { + message = "By clicking 'Yes' you are agreeing to the Terms and Conditions of this Letter of Credit."; + message += this.props.user !== 'bob' ? " The letter will now be sent to the next participant for approval." : "" + } else if (this.props.modalType === 'REJECT') { + message = "By clicking 'Yes' you are rejecting this application and the Letter of Credit will be closed. Once rejected, you will be unable to reopen this Letter of Credit."; + } else if (this.props.modalType === 'PAY') { + message = "By clicking 'Yes' you are agreeing that the applicant has received the goods in good condition, and you are willing to transfer the payment to the exporting bank."; + } else { + message = "By clicking 'Yes' you are agreeing that the Terms and Conditions of this Letter of Credit have been met, and that the payment has been made to the beneficiary."; + } + + return message; + } + + render() { + if(this.props.show) { + let message = this.getMessage(); + let containerClasses = "container " + (this.props.modalType === 'SHIP' ? "shipContainer" : ""); + let content; + + if(this.props.modalType === 'SHIP') { + content = ( +
+

Upload an Invoice

+ + + + +
+ +
+ { this.state.isLoading &&

Please wait...

} +
+ + +
+
+ ); + } + else { + content = ( +
+

Are you sure you want to {this.props.modalType.toLowerCase()} this letter?

+

{message}

+
+ + +
+
+ ); + } + + return ( +
+
+ {content} +
+
+ ); + } + else { + return (
); + } + } +} + +export default Modal; diff --git a/packages/letters-of-credit/src/components/Pages/AlicePage/AlicePage.js b/packages/letters-of-credit/src/components/Pages/AlicePage/AlicePage.js new file mode 100644 index 00000000..36930648 --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/AlicePage/AlicePage.js @@ -0,0 +1,144 @@ +import React, { Component } from 'react'; +import '../../../stylesheets/css/main.css'; +import { Redirect } from 'react-router-dom'; +import axios from 'axios'; +import UserDetails from '../../UserDetails/UserDetails.js'; +import LoCCard from '../../LoCCard/LoCCard.js'; +import LoCApplyCard from '../../LoCCard/LoCApplyCard.js'; +import Config from '../../../utils/config'; +import aliceUsernameIcon from '../../../resources/images/viewLocIcon.png'; + +class AlicePage extends Component { + constructor(props) { + super(props); + this.state = { + userDetails: {}, + letters: [], + gettingLetters: false, + switchUser: this.props.switchUser, + callback: this.props.callback, + redirect: false, + redirectTo: '' + } + this.handleOnClick = this.handleOnClick.bind(this); + this.config = new Config(); + } + + handleOnClick(user) { + this.state.switchUser(user); + this.setState({redirect: true, redirectTo: user}); + } + + componentDidMount() { + document.title = "Alice - Bank of Dinero"; + // open a websocket + this.connection = new WebSocket(this.config.restServer.webSocketURL); + this.connection.onmessage = ((evt) => { + this.getLetters(); + }); + + // make rest calls + this.getUserInfo(); + this.getLetters(); + } + + componentWillUnmount() { + this.connection.close(); + } + + getUserInfo() { + let userDetails = {}; + let cURL = this.config.restServer.httpURL+'/Customer/alice'; + axios.get(cURL) + .then(response => { + userDetails = response.data; + }) + .then(() => { + let bankURL = this.config.restServer.httpURL+'/Bank/'+userDetails.bank.split('#')[1]; + return axios.get(bankURL) + }) + .then(response => { + userDetails.bank = response.data.name; + this.setState ({ + userDetails: userDetails + }); + }) + .catch(error => { + console.log(error); + }); + } + + getLetters() { + this.setState({gettingLetters: true}); + axios.get(this.config.restServer.httpURL+'/LetterOfCredit') + .then(response => { + // sort the LOCs by descending ID (will display the most recent first) + response.data.sort((a,b) => b.letterId.localeCompare(a.letterId)); + // only want to display the first 5 LOCs + let activeLetters = response.data.slice(0,5); + this.setState ({ + letters: activeLetters, + gettingLetters: false + }); + }) + .catch(error => { + console.log(error); + }); + } + + generateCard(i) { + return ( + + ); + } + + render() { + if (this.state.redirect) { + return ; + } + + if(this.state.userDetails.name && !this.state.gettingLetters) { + let username = this.state.userDetails.name + ", Customer of " + this.state.userDetails.bank; + + let cardsJSX = []; + if(this.state.letters.length) { + for(let i = 0; i < this.state.letters.length; i++) { + cardsJSX.push(this.generateCard(i)); + } + cardsJSX.push(
 
); + } + + return ( +
+
+ {username} +
+ Change account details + View Transaction History + Make Transaction + Current Balance: €15,276.00 +
+
+
+
+

Welcome back {this.state.userDetails.name}

+ +
+
+
+ + {cardsJSX} +
+
+ ); + } else { + return ( +
+ Loading... +
+ ); + } + } +} + +export default AlicePage; diff --git a/packages/letters-of-credit/src/components/Pages/BobPage/BobPage.js b/packages/letters-of-credit/src/components/Pages/BobPage/BobPage.js new file mode 100644 index 00000000..931107fe --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/BobPage/BobPage.js @@ -0,0 +1,173 @@ +import React, { Component } from 'react'; +import '../../../stylesheets/css/main.css'; +import { Redirect } from 'react-router-dom'; +import axios from 'axios'; +import Alert from '../../Alert/Alert.js' +import UserDetails from '../../UserDetails/UserDetails.js'; +import LoCCard from '../../LoCCard/LoCCard.js'; +import LoCApplyCard from '../../LoCCard/LoCApplyCard.js'; +import Config from '../../../utils/config'; + +class BobPage extends Component { + constructor(props) { + super(props); + this.state = { + userDetails: {}, + letters: [], + gettingLetters: false, + callback: this.props.callback, + alert: false, + redirect: false, + redirectTo: '' + } + this.config = new Config(); + } + + componentDidMount() { + document.title = "Bob - Eastwood Banking"; + // open a websocket + this.connection = new WebSocket(this.config.restServer.webSocketURL); + this.connection.onmessage = ((evt) => { + let eventInfo = JSON.parse(evt.data); + eventInfo = eventInfo.$class.split('.').pop(); + this.getLetters(); + if(eventInfo === 'CloseEvent') { + this.setState({ + alert: true + }); + } else { + if (this.state.alert) { + this.setState({ + alert: false + }); + } + } + }); + + // make rest calls + this.getUserInfo(); + this.getLetters(); + } + + componentWillUnmount() { + this.connection.close(); + } + + getUserInfo() { + let userDetails = {}; + let cURL = this.config.restServer.httpURL+'/Customer/bob'; + axios.get(cURL) + .then(response => { + userDetails = response.data; + }) + .then(() => { + let bankURL = this.config.restServer.httpURL+'/Bank/'+userDetails.bank.split('#')[1]; + return axios.get(bankURL) + }) + .then(response => { + userDetails.bank = response.data.name; + this.setState ({ + userDetails: userDetails + }); + }) + .catch(error => { + console.log(error); + }); + } + + getLetters() { + this.setState({gettingLetters: true}); + axios.get(this.config.restServer.httpURL+'/LetterOfCredit') + .then(response => { + // sort the LOCs by descending ID + response.data.sort((a,b) => b.letterId.localeCompare(a.letterId)); + // only want to display the first 5 LOCs + let activeLetters = response.data.slice(0,5); + this.setState ({ + letters: activeLetters, + gettingLetters: false + }); + }) + .catch(error => { + console.log(error); + }); + } + + generateCard(i) { + // should only show LOCs that are ready for Bob to approve + if (this.state.letters[i].approval.includes('resource:org.example.loc.BankEmployee#ella')){ + if(i < this.state.letters.length){ + return ( + + ); + } + } else { + return
; + } + } + + getBalance() { + let balance = 12000; + this.state.letters.map(i => { + balance += i.status === 'CLOSED' ? i.productDetails.quantity * i.productDetails.pricePerUnit * 1.15 : 0; + }); + return balance.toLocaleString(undefined, {minimumFractionDigits: 2}); + } + + getBalanceIncrease() { + let increase = 0; + if (this.state.letters.length) { + let closedLetter = this.state.letters[0]; + increase = (closedLetter.productDetails.quantity * closedLetter.productDetails.pricePerUnit * 1.15); + } + return increase; + } + + render() { + if (this.state.redirect) { + return ; + } + + if(this.state.userDetails.name && !this.state.gettingLetters) { + let username = this.state.userDetails.name + ", Customer of " + this.state.userDetails.bank; + + let cardsJSX = []; + if(this.state.letters.length) { + for(let i = 0; i < this.state.letters.length; i++) { + cardsJSX.push(this.generateCard(i)); + } + cardsJSX.push(
 
); + } + + return ( +
+
+ {username} +
+
+

Welcome back {this.state.userDetails.name}

+

${this.getBalance().toLocaleString()}

+ +
+
+
+ +
+
+
+ + {cardsJSX} +
+
+ ); + } else { + return ( +
+ Loading... +
+ ); + } + } +} + +export default BobPage; diff --git a/packages/letters-of-credit/src/components/Pages/EllaPage/EllaPage.js b/packages/letters-of-credit/src/components/Pages/EllaPage/EllaPage.js new file mode 100644 index 00000000..3b1b3e31 --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/EllaPage/EllaPage.js @@ -0,0 +1,186 @@ +import React, { Component } from 'react'; +import '../../../stylesheets/css/main.css'; +import { Redirect } from 'react-router-dom'; +import axios from 'axios'; +import Table from '../../Table/Table.js'; +import Config from '../../../utils/config'; + +class EllaPage extends Component { + constructor(props) { + super(props); + this.state = { + userDetails: {}, + letters: [], + gettingLetters: false, + switchUser: this.props.switchUser, + callback: this.props.callback, + redirect: false, + redirectTo: '', + isLetterOpen: false + } + this.handleOnClick = this.handleOnClick.bind(this); + this.openLetter = this.openLetter.bind(this); + this.config = new Config(); + } + + handleOnClick(user) { + this.state.switchUser(user); + this.setState({redirect: true, redirectTo: user}); + } + + openLetter(i) { + this.props.callback(this.state.letters[i], false); + this.setState({isLetterOpen: true, redirectTo: '/ella/loc/' + this.state.letters[i].letterId}); + } + + componentDidMount() { + document.title = "Ella - Eastwood Banking"; + // open a websocket + this.connection = new WebSocket(this.config.restServer.webSocketURL); + this.connection.onmessage = ((evt) => { + this.getLetters(); + }); + + let userDetails = {}; + let cURL = this.config.restServer.httpURL+'/BankEmployee/ella'; + axios.get(cURL) + .then(response => { + userDetails = response.data; + }) + .then(() => { + let bankURL = this.config.restServer.httpURL+'/Bank/'+userDetails.bank.split('#')[1]; + return axios.get(bankURL) + }) + .then(response => { + userDetails.bank = response.data.name; + this.setState ({ + userDetails: userDetails + }); + }) + .catch(error => { + console.log(error); + }); + + this.getLetters(); + } + + componentWillUnmount() { + this.connection.close(); + } + + getLetters() { + this.setState({gettingLetters: true}); + axios.get(this.config.restServer.httpURL+'/LetterOfCredit') + .then(response => { + // sort the LOCs by descending ID (will display the most recent first) + response.data.sort((a,b) => b.letterId.localeCompare(a.letterId)); + this.setState ({ + letters: response.data, + gettingLetters: false + }); + }) + .catch(error => { + console.log(error); + }); + } + + generateStatus(letter) { + let status = ''; + let statusColour; + if (letter.status === 'AWAITING_APPROVAL') { + if (!letter.approval.includes('resource:org.example.loc.BankEmployee#ella')) { + status = 'Awaiting approval from YOU'; + } else if (!letter.approval.includes('resource:org.example.loc.Customer#bob')) { + status = 'Awaiting approval from Beneficiary'; + } + statusColour = "red"; + } + else if (letter.status === 'READY_FOR_PAYMENT'){ + status = 'Payment Made'; + statusColour = "blue"; + } + else { + status = letter.status.toLowerCase(); + status = status.charAt(0).toUpperCase() + status.slice(1); + + if(letter.status === 'CLOSED') { + statusColour = "green"; + } + else { + statusColour = "blue"; + } + } + return {status: status, statusColour: statusColour}; + } + + generateRow(i) { + // should only show LOCs that are ready for Ella to approve + if (this.state.letters[i].approval.includes('resource:org.example.loc.BankEmployee#matias')) { + let submitter = "Alice Hamilton"; + let company = "QuickFix IT"; + if(this.state.letters[i].applicant === 'resource:org.example.loc.Customer#bob') { + submitter = "Bob Appleton"; + company = "Conga Computers" + } + let status = this.generateStatus(this.state.letters[i]); + let statusStyle = { + backgroundColor: status.statusColour + } + return ( + this.openLetter(i)}> + {this.state.letters[i].letterId} + {submitter} + {company} + + {status.status} + + + ); + } else { + return
; + } + } + + render() { + if (this.state.redirect) { + return ; + } + else if(this.state.isLetterOpen) { + return ; + } + + if(this.state.userDetails.name && !this.state.gettingLetters) { + let username = this.state.userDetails.name + ", Employee at " + this.state.userDetails.bank; + + let rowsJSX = []; + if(this.state.letters.length) { + for(let i = 0; i < this.state.letters.length; i++) { + rowsJSX.push(this.generateRow(i)) + } + } + + return ( +
+
+ {username} +
+
+ {this.state.userDetails.bank} +

Welcome back {this.state.userDetails.name}

+
+
+ + + + ); + } else { + return( +
+ Loading... +
+ ); + } + } +} + +export default EllaPage; diff --git a/packages/letters-of-credit/src/components/Pages/MatiasPage/MatiasPage.js b/packages/letters-of-credit/src/components/Pages/MatiasPage/MatiasPage.js new file mode 100644 index 00000000..06bdcb13 --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/MatiasPage/MatiasPage.js @@ -0,0 +1,183 @@ +import React, { Component } from 'react'; +import '../../../stylesheets/css/main.css'; +import { Redirect } from 'react-router-dom'; +import axios from 'axios'; +import Table from '../../Table/Table.js'; +import Config from '../../../utils/config'; +import matiasUsernameIcon from '../../../resources/images/viewLocIcon.png'; + +class MatiasPage extends Component { + constructor(props) { + super(props); + this.state = { + userDetails: {}, + letters: [], + gettingLetters: false, + switchUser: this.props.switchUser, + callback: this.props.callback, + redirect: false, + redirectTo: '', + isLetterOpen: false + } + this.handleOnClick = this.handleOnClick.bind(this); + this.openLetter = this.openLetter.bind(this); + this.config = new Config(); + } + + handleOnClick(user) { + this.state.switchUser(user); + this.setState({redirect: true, redirectTo: user}); + } + + openLetter(i) { + this.props.callback(this.state.letters[i], false); + this.setState({isLetterOpen: true, redirectTo: '/matias/loc/' + this.state.letters[i].letterId}); + } + + componentDidMount() { + document.title = "Matías - Bank of Dinero"; + // open a websocket + this.connection = new WebSocket(this.config.restServer.webSocketURL); + this.connection.onmessage = ((evt) => { + this.getLetters(); + }); + + let userDetails = {}; + let cURL = this.config.restServer.httpURL+'/BankEmployee/matias'; + axios.get(cURL) + .then(response => { + userDetails = response.data; + }) + .then(() => { + let bankURL = this.config.restServer.httpURL+'/Bank/'+userDetails.bank.split('#')[1]; + return axios.get(bankURL) + }) + .then(response => { + userDetails.bank = response.data.name; + this.setState ({ + userDetails: userDetails + }); + }) + .catch(error => { + console.log(error); + }); + + this.getLetters(); + } + + componentWillUnmount() { + this.connection.close(); + } + + getLetters() { + this.setState({gettingLetters: true}); + axios.get(this.config.restServer.httpURL+'/LetterOfCredit') + .then(response => { + // sort the LOCs by descending ID (will display the most recent first) + response.data.sort((a,b) => b.letterId.localeCompare(a.letterId)); + this.setState ({ + letters: response.data, + gettingLetters: false + }); + }) + .catch(error => { + console.log(error); + }); + } + + generateStatus(letter) { + let status = ''; + let statusColour; + if (letter.status === 'AWAITING_APPROVAL') { + if (!letter.approval.includes('resource:org.example.loc.BankEmployee#matias')) { + status = 'Awaiting approval from YOU'; + } else if (!letter.approval.includes('resource:org.example.loc.BankEmployee#ella')) { + status = 'Awaiting approval from Exporting Bank'; + } else if (letter.approval.includes('resource:org.example.loc.BankEmployee#ella') && !letter.approval.includes('resource:org.example.loc.Customer#bob')) { + status = 'Awaiting approval from Beneficiary'; + } + statusColour = "red"; + } else if (letter.status === 'READY_FOR_PAYMENT'){ + status = 'Payment Made'; + statusColour = "blue"; + } + else { + status = letter.status.toLowerCase(); + status = status.charAt(0).toUpperCase() + status.slice(1); + + if(letter.status === 'CLOSED') { + statusColour = "green"; + } + else { + statusColour = "blue"; + } + } + return {status: status, statusColour: statusColour}; + } + + generateRow(i) { + let submitter = "Alice Hamilton"; + let company = "QuickFix IT"; + if(this.state.letters[i].applicant === 'resource:org.example.loc.Customer#bob') { + submitter = "Bob Appleton"; + company = "Conga Computers"; + } + let status = this.generateStatus(this.state.letters[i]); + let statusStyle = { + backgroundColor: status.statusColour + } + return ( + this.openLetter(i) }> + + + + + + ); + } + + render() { + if(this.state.isLetterOpen) { + return ; + } + else if (this.state.redirect) { + return ; + } + + if(this.state.userDetails.name && !this.state.gettingLetters) { + let username = this.state.userDetails.name + ", Employee at " + this.state.userDetails.bank; + + let rowsJSX = []; + if(this.state.letters.length) { + for(let i = 0; i < this.state.letters.length; i++) { + rowsJSX.push(this.generateRow(i)) + } + } + + return ( +
+
+ {username} + Viewing All Business Acccounts +
+
+

Welcome back {this.state.userDetails.name}

+
+
+
{this.state.letters[i].letterId}{submitter}{company} + {status.status} +
+ + + ); + } else { + return( +
+ Loading... +
+ ); + } + } +} + +export default MatiasPage; diff --git a/packages/letters-of-credit/src/components/Pages/Page.js b/packages/letters-of-credit/src/components/Pages/Page.js new file mode 100644 index 00000000..02888859 --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/Page.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; +import Modal from '../Modal/Modal.js'; + +class Page extends Component { + render() { + return ( +
+ + {this.props.contents} +
+ ); + } +} + +export default Page; diff --git a/packages/letters-of-credit/src/components/Pages/TutorialPage/TutorialPage.js b/packages/letters-of-credit/src/components/Pages/TutorialPage/TutorialPage.js new file mode 100644 index 00000000..83cfe789 --- /dev/null +++ b/packages/letters-of-credit/src/components/Pages/TutorialPage/TutorialPage.js @@ -0,0 +1,186 @@ +import React, { Component } from 'react'; +import '../../../stylesheets/css/main.css'; +import Config from '../../../utils/config'; + +class TutorialPage extends Component { + + constructor() { + super(); + this.config = new Config(); + } + + componentDidMount() { + document.title = "Tutorial"; + } + + render() { + return ( +
+ + + +
+

Scenario

+ +

In this sample you will take on the role of four participants to see how blockchain can be used to track letters of credit. These participants are:

+ +

Alice Hamilton - Owner of QuickFix IT, a company specialising in the sale of computers.

+ +

Bob Appleton - Owner of Conga Computers, a manufacturer of computers.

+ +

Matías - Employee of Bank of Dinero, the bank which Alice has an account with.

+ +

Ella - Employee of the Eastwood Banking, the bank which Bob has an account with.

+ +

The above participants have varying roles in the scenario with Alice playing the applicant, Bob the beneficiary, Matías the issuing bank and Ella the exporter bank.

+ +

In the scenario that this sample covers Alice and Bob have agreed that Alice will purchase computers from Bob's next shipment (due to arrive in 14 days) and have agreed a price for these. Alice unfortunately does not have enough money in her account to cover the entire cost of the purchase and therefore she is requesting a letter of credit to cover this cost from her Bank, Bank of Dinero. This sample will cover how this letter of credit is tracked using blockchain.

+ +

Tutorial

+ +

Setting up the demo

+ +

Open each participant's application in a seperate tab. Alice's banking app, Matías' bank employee view, Ella's bank employee view and Bob's banking app.

+ +

Applying for a letter of credit

+ +

On Alice's screen use the button to apply for a letter of credit.

+ +

The letter of credit requires certain information, the details of the applicant (Alice), the details of the beneficiary (Bob), the details of the product it relates to and terms and conditions that must be met.

+ +

Update the product details using the edit button. Enter computers for the type, 100 for the quantity and 1200 for the price per unit. Click save to confirm your changes.

+ +

Update the terms and conditions to suit the scenario using the edit button and then change the second term to 14 days. Press the save button to confirm the changes.

+ +

Confirm the request for a letter of credit by clicking Start approval process and pressing Yes to the modal warning that Alice is agreeing to the terms of the letter and that it will be sent to her bank for approval.

+ +

On Alice's screen you should now see a card containing the letter of credit.

+ +

NOTE: For the purpose of this sample the details of Bob are prefilled in for the letter of credit.

+ +

Issuing bank approval

+ +

Now that the letter of credit request has been sent, Matías must review it and accept or reject the application in his role at the Bank of Dinero.

+ +

Navigate to Matías' screen and you will see that there is a new request for a letter of credit from Alice Hamilton. Click on the request to view it in full.

+ +

Within the letter of credit screen the bar at the top show Matías the current status of the letter. On the right hand side of the screen Matías is able to see the history stored in the blockchain of the letter of credit.

+ +

Review the letter of credit request and accept the application by clicking I accept the application and pressing Yes to the modal warning that Matías is agreeing to the terms of the letter and that it will be sent to the exporting bank for their approval.

+ +

Exporting bank approval

+ +

The letter of credit has now arrived at the exporting bank, Eastwood Banking, and requires their approval. It is Ella's responsibility to approve the letter of credit on behalf of Eastwood Banking.

+ +

Navigate to Ella's screen and you will see that the letter of credit from Alice and approved by the Bank of Dinero requires review. Click on the request to view it in full.

+ +

Review the product details and terms and conditions are acceptable, notice that the currency has been converted to Bob's local currency. Accept the application by clicking I accept the application. Press Yes to the modal warning that Ella is agreeing to the terms of the letter and that it will be sent to the beneficiary for their approval.

+ +

Exporter approval

+ +

After the letter of credit has been approved by both the issuing bank and the exporting bank it is visible to Bob.

+ +

Navigate to Bob's screen and click the arrow on the letter of credit to view it.

+ +

Review the letter of credit and accept the application by clicking I accept the application and pressing Yes to the modal warning that Bob is agreeing to the terms of the letter.

+ +

The letter of credit has now been accepted by all parties, and the transaction can continue.

+ +

Declaring the goods as shipped

+ +

After Bob at Conga Computers has shipped the equipment to Alice at QuickFix IT, he can use his banking application to update the letter of credit to verify the shipment.

+ +

In Bob's home screen use the toggle on the letter of credit card to mark that the goods have been shipped. In the popup check the checkbox next to shipping-invoice.pdf and press upload. This writes to the letter of credit a hash of his shipping documents as proof.

+ +

The letter of credit is visible to Alice at all steps, so she can see that the goods have been shipped.

+ +

Declaring the good as received

+ +

Upon receipt of the goods, Alice can mark the goods as received on the letter of credit.

+ +

In Alice's home screen use the toggle in the letter of credit card to mark that the goods have been received.

+ +

Accepting the order and updating the letter of credit, Alice agrees that what she has received matches the terms specified in the letter of credit.

+ +

Issuing payment

+ +

After Alice has verified receipt of the goods, the issuing bank can approve the payment. The issuing bank will perform their own check of the goods and pay the letter of credit if they agree that they match the terms of the letter.

+ +

On Matías' screen click the letter of credit and press Ready for payment and press Yes to the modal warning that Matías is agreeing the goods arrived matching the terms of the letter and the balance should be transferred.

+ +

Closing the Letter of Credit

+ +

The issuing bank has now confirmed that they are happy with the goods and have made the payment, the exporting bank can close the letter of credit and deposit the funds in the beneficiary's account, namely Bob from Conga Computers.

+ +

In Ella's screen click the letter of credit and press Close this letter of credit.

+ +

Confirming completion

+ +

Now that the letter of credit is closed Bob will have received the full value of the goods.

+ +

On Bob's screen review his bank balance to ensure that the payment has been made.

+ +

Benefit of blockchain

+ +
    +
  • Clarity - Using blockchain there is a single source for each participant to get data on the letter of credit. This means all participants have the same up to date view of the status of the letter.
  • + +
  • Speed - Each participant can update the letter when they are required without the need for back and forth messages with other participants to ensure that they have the latest details about the letter.
  • + +
  • Security - With a single source of truth participants can see the entire history of other participants' actions around letters of credit. Using this they can perform risk analysis to decide whether or not to approve a letter of credit relating to them.
  • +
+

Next steps

+ +

This sample works using {this.config.playground.name}. Check out the model and business logic behind this sample here, or browse the generated REST APIs here.

+
+
+ ); + } +} + +export default TutorialPage; \ No newline at end of file diff --git a/packages/letters-of-credit/src/components/Step/Step.js b/packages/letters-of-credit/src/components/Step/Step.js new file mode 100644 index 00000000..b879e134 --- /dev/null +++ b/packages/letters-of-credit/src/components/Step/Step.js @@ -0,0 +1,68 @@ +import React, {Component} from 'react'; +import Config from '../../utils/config'; +import '../../stylesheets/css/main.css'; + +class Step extends Component { + constructor(props) { + super(props); + + this.state = { + stepType: this.props.stepType, + stepNumber: this.props.stepNumber, + stepMessage: this.props.stepMessage, + stepPosition: this.props.stepPosition + } + + this.config = new Config(); + } + + generateStep(){ + let stepJSX = []; + console.log(this.state.stepPosition) + let firstConnectorClass = this.state.stepPosition === "first" ? "blankStepConnector" : "stepConnector"; + let lastConnectorClass = this.state.stepPosition === "last" ? "blankStepConnector" : "stepConnector"; + if (this.state.stepType === "activeStep"){ + stepJSX.push( +
+
+
+

Step {this.state.stepNumber + 1}


+

{this.state.stepMessage}

+
+
+
+
+
+
+
+
+
+ ); + } + else { + stepJSX.push( +
+
+
+
+
+
+
+ ); + } + return stepJSX; + } + + render() { + let step = this.generateStep(); + return ( +
+ {step.map((jsx) => { + return (jsx); + })} +
+ ); + } +} + +export default Step; diff --git a/packages/letters-of-credit/src/components/Stepper/Stepper.js b/packages/letters-of-credit/src/components/Stepper/Stepper.js new file mode 100644 index 00000000..41c8d7e5 --- /dev/null +++ b/packages/letters-of-credit/src/components/Stepper/Stepper.js @@ -0,0 +1,54 @@ +import React, {Component} from 'react'; +import Config from '../../utils/config'; +import '../../stylesheets/css/main.css'; +import Step from '../../components/Step/Step.js' + +class Stepper extends Component { + constructor(props) { + super(props); + + this.state = { + steps: this.props.steps, + activeStep: this.props.activeStep + } + + this.config = new Config(); + } + + generateSteps(){ + let stepsJSX = []; + let stepType = "completedStep"; + + for (var i = 0; i < this.state.steps.length; i++){ + let step = this.state.steps[i]; + let stepPos; + if (i === 0){ + stepPos = "first"; + } + else if (i === this.state.steps.length-1){ + stepPos = "last"; + } + if (i === this.state.activeStep){ + stepsJSX.push(); + stepType = "uncompletedStep"; + } + else { + stepsJSX.push(); + } + } + return stepsJSX; + } + + render() { + let steps = this.generateSteps(); + return ( +
+ {steps.map((jsx) => { + return (jsx); + })} +
+ ); + } +} + +export default Stepper; diff --git a/packages/letters-of-credit/src/components/Table/Table.js b/packages/letters-of-credit/src/components/Table/Table.js new file mode 100644 index 00000000..ef4cfd86 --- /dev/null +++ b/packages/letters-of-credit/src/components/Table/Table.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; + +class Table extends Component { + render() { + return( +
+
+ Letters of Credit Applications +
+
+ + + + + + + + {this.props.rows} + +
Ref numberApplicant NameBusiness AccountStatus
+
+ ); + } +} + +export default Table; diff --git a/packages/letters-of-credit/src/components/UserDetails/UserDetails.js b/packages/letters-of-credit/src/components/UserDetails/UserDetails.js new file mode 100644 index 00000000..838fe4df --- /dev/null +++ b/packages/letters-of-credit/src/components/UserDetails/UserDetails.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react'; +import '../../stylesheets/css/main.css'; + +class UserDetails extends Component { + constructor(props) { + super(props); + this.state = { + name: this.props.name, + companyName: this.props.companyName, + swiftCode: this.props.swiftCode, + IBAN: this.props.IBAN, + } + } + + render() { + return ( +
+

Business Account Details

+
Name: {this.state.name}
+
Company Name: {this.state.companyName}
+
IBAN: {this.state.IBAN}
+
SWIFT code: {this.state.swiftCode}
+
+ ); + } +} + +export default UserDetails; diff --git a/packages/letters-of-credit/src/index.css b/packages/letters-of-credit/src/index.css new file mode 100644 index 00000000..dc455d7a --- /dev/null +++ b/packages/letters-of-credit/src/index.css @@ -0,0 +1,9 @@ +body { + margin: 0; + padding: 0; + min-height: 100%; +} + +html, body, #app, #app>div { + height: 100% +} diff --git a/packages/letters-of-credit/src/index.js b/packages/letters-of-credit/src/index.js new file mode 100644 index 00000000..60e187e7 --- /dev/null +++ b/packages/letters-of-credit/src/index.js @@ -0,0 +1,20 @@ +import React from 'react'; +import {render} from 'react-dom'; +import {createStore} from 'redux'; +import {Provider} from 'react-redux'; +import {BrowserRouter} from 'react-router-dom'; +import './index.css'; +import App from './App.js'; +import rootReducer from './reducers/reducers'; + +let store = createStore(rootReducer); +let rootElement = document.getElementById('root'); + +render( + + + + + , + rootElement +); diff --git a/packages/letters-of-credit/src/index.scss b/packages/letters-of-credit/src/index.scss new file mode 100644 index 00000000..eb5c1096 --- /dev/null +++ b/packages/letters-of-credit/src/index.scss @@ -0,0 +1 @@ +@import 'App'; diff --git a/packages/letters-of-credit/src/logo.svg b/packages/letters-of-credit/src/logo.svg new file mode 100644 index 00000000..6b60c104 --- /dev/null +++ b/packages/letters-of-credit/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/letters-of-credit/src/reducers/reducers.js b/packages/letters-of-credit/src/reducers/reducers.js new file mode 100644 index 00000000..d14d8182 --- /dev/null +++ b/packages/letters-of-credit/src/reducers/reducers.js @@ -0,0 +1,51 @@ +import {combineReducers} from 'redux' +import * as actions from '../actions/actions' + +const initialState = { + applicant: { + name: 'Alice Hamilton', + companyName: 'QuickFix IT', + IBAN: 'IT60 9876 5321 9090', + swiftCode: 'BKDOIT60', + bankName: 'Bank of Dinero' + }, + beneficiary: { + name: 'Bob Appleton', + companyName: 'Conga Computers', + IBAN: 'US22 1234 5678 0101', + swiftCode: 'EWBKUS22', + bankName: 'Eastwood Banking' + }, + productDetails: { + type: "None", + quantity: 0, + pricePerUnit: 0, + total: 0 + }, + rules: [ + {ruleText: "The correct quantity of product has been delivered."}, + {ruleText: "The product was received within 30 days of the placement of the order."}, + {ruleText: "The product is not damaged and functions as expected."} + ] +} + +const getLetterInputReducer = (state = initialState, action) => { + switch (action.type) { + case actions.GET_APPLICANT: + return { ...state, applicant: action.payload }; + case actions.GET_BENEFICIARY: + return { ...state, beneficiary: action.payload }; + case actions.GET_PRODUCT_DETAILS: + return { ...state, productDetails: action.payload }; + case actions.GET_RULES: + return { ...state, rules: [...state.rules, action.payload] }; + default: + return state; + } +}; + +const rootReducer = combineReducers({ + getLetterInputReducer +}) + +export default rootReducer; diff --git a/packages/letters-of-credit/src/registerServiceWorker.js b/packages/letters-of-credit/src/registerServiceWorker.js new file mode 100644 index 00000000..a3e6c0cf --- /dev/null +++ b/packages/letters-of-credit/src/registerServiceWorker.js @@ -0,0 +1,117 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' + ); + }); + } else { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/packages/letters-of-credit/src/resources/backgrounds/BACKGROUND3.svg b/packages/letters-of-credit/src/resources/backgrounds/BACKGROUND3.svg new file mode 100644 index 00000000..7dc93496 --- /dev/null +++ b/packages/letters-of-credit/src/resources/backgrounds/BACKGROUND3.svg @@ -0,0 +1,33 @@ + + + + Group 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/letters-of-credit/src/resources/backgrounds/largebackgroundslice.png b/packages/letters-of-credit/src/resources/backgrounds/largebackgroundslice.png new file mode 100644 index 00000000..8c90cfdc Binary files /dev/null and b/packages/letters-of-credit/src/resources/backgrounds/largebackgroundslice.png differ diff --git a/packages/letters-of-credit/src/resources/backgrounds/smallbackgroundslice.png b/packages/letters-of-credit/src/resources/backgrounds/smallbackgroundslice.png new file mode 100644 index 00000000..cd950db0 Binary files /dev/null and b/packages/letters-of-credit/src/resources/backgrounds/smallbackgroundslice.png differ diff --git a/packages/letters-of-credit/src/resources/editIcon.svg b/packages/letters-of-credit/src/resources/editIcon.svg new file mode 100644 index 00000000..91a391c4 --- /dev/null +++ b/packages/letters-of-credit/src/resources/editIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/letters-of-credit/src/resources/images/left-arrow.svg b/packages/letters-of-credit/src/resources/images/left-arrow.svg new file mode 100644 index 00000000..5aa5406f --- /dev/null +++ b/packages/letters-of-credit/src/resources/images/left-arrow.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/letters-of-credit/src/resources/images/right-arrow.svg b/packages/letters-of-credit/src/resources/images/right-arrow.svg new file mode 100644 index 00000000..1a5ca66b --- /dev/null +++ b/packages/letters-of-credit/src/resources/images/right-arrow.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/letters-of-credit/src/resources/images/viewLocIcon.png b/packages/letters-of-credit/src/resources/images/viewLocIcon.png new file mode 100644 index 00000000..ed086307 Binary files /dev/null and b/packages/letters-of-credit/src/resources/images/viewLocIcon.png differ diff --git a/packages/letters-of-credit/src/resources/images/viewLocIconBob.png b/packages/letters-of-credit/src/resources/images/viewLocIconBob.png new file mode 100644 index 00000000..7daebe9f Binary files /dev/null and b/packages/letters-of-credit/src/resources/images/viewLocIconBob.png differ diff --git a/packages/letters-of-credit/src/stylesheets/Gulpfile.js b/packages/letters-of-credit/src/stylesheets/Gulpfile.js new file mode 100644 index 00000000..7be1e0c5 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/Gulpfile.js @@ -0,0 +1,13 @@ +var gulp = require('gulp'); +var sass = require('gulp-sass'); + +gulp.task('styles', function() { + gulp.src('sass/**/*.scss') + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest('./css/')); +}); + +//Watch task +gulp.task('default',function() { + gulp.watch('sass/**/*.scss',['styles']); +}); diff --git a/packages/letters-of-credit/src/stylesheets/css/main.css b/packages/letters-of-credit/src/stylesheets/css/main.css new file mode 100644 index 00000000..f4439d94 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/css/main.css @@ -0,0 +1,1198 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600"); +.alicePageContainer { + width: 100%; + height: 100vh; + background-image: url("../../resources/backgrounds/BACKGROUND3.svg"); + background-repeat: no-repeat; + background-color: #3B51DC; + background-size: contain; + display: flex; + flex-direction: column; } + +.loadingSpan { + margin: auto; + color: white; } + +.flexDiv { + display: flex; + flex-direction: row; } + +.aliceHeaderDiv { + width: 100%; + height: 100px; } + +.aliceUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; } + +.aliceUsername { + float: left; + margin: 30px; + font-weight: 300; + font-size: 90%; + color: white; + text-decoration: none; } + +.aliceUsernameIcon { + margin-right: 5px; } + +.aliceMenuItems { + position: absolute; + right: 0; + margin-top: 30px; } + .aliceMenuItems > span { + margin: 30px; + color: white; + font-weight: 300; + font-size: 10pt; } + +.aliceWelcomeDiv > h1 { + margin-left: 60px; + margin-top: 45px; + margin-bottom: 40px; + color: white; } + +.aliceWelcomeMessage { + font-weight: 300; + font-size: 44px; + color: #ffffff; + letter-spacing: 0; } + +.alertsDiv { + display: flex; + flex-direction: column; + float: right; } + +.locDiv { + display: flex; + flex-direction: row; + width: 100%; + overflow-x: auto; } + +.currentBalance { + margin-top: 5px; + margin-left: -5px; + border: 1px solid #273ec6; + border-radius: 20px; + background-color: #273ec6; + color: white; + padding: 8px 20px; } + +.cardSpace { + display: inline-block; + margin-left: 60px; } + +.bobPageContainer { + width: 100%; + height: 100vh; + background-color: #28293c; + display: flex; + flex-direction: column; } + +.loadingSpan { + margin: auto; + color: white; } + +.flexDiv { + display: flex; + flex-direction: row; } + +.bobHeaderDiv { + width: 100%; + height: 10vh; } + +.bobUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #bc5183; + border-radius: 15px; + background-color: #bc5183; + display: inline-block; + margin-right: 5px; } + +.bobUsername { + float: left; + margin: 30px; + font-weight: 300; + font-size: 90%; + color: white; + text-decoration: none; } + +.infoDivBob { + height: 30vh; } + +.bobWelcomeDiv { + text-align: center; + margin-left: 0; + margin-top: 0; + color: white; + font-size: 20px; } + +.bobDetailsDiv { + margin-top: 1vh; + color: white; } + +.alertsDiv { + display: flex; + flex-direction: column; } + +.locDivBob { + display: flex; + flex-direction: row; + width: 100%; + overflow-x: auto; + padding: 10px 0; } + +.LoCCardConnector { + border: 5px solid #7646c0; + width: 10px; + height: 10px; } + +#welcomeMessage { + font-size: 20px; + font-weight: 300; } + +#accountBalance { + font-size: 55px; + font-weight: 300; } + +.ellaPageContainer { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; } + +.ellaLoadingSpan { + margin: auto; } + +.flexDiv { + display: flex; + flex-direction: row; } + +.ellaHeaderDiv { + background-color: #28293c; + width: 100%; + color: white; } + +.ellaUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #bc5183; + border-radius: 15px; + background-color: #bc5183; + display: inline-block; + margin-right: 5px; } + +.ellaUsername { + float: left; + margin: 15px 0 0 30px; + font-weight: 300; + font-size: 90%; } + +.ellaWelcomeDiv { + background-color: #28293c; + color: white; + text-align: center; + text-shadow: 0 1px #4e3747; + padding-top: 24px; } + +.ellaWelcomeDiv > span { + font-weight: 500; + font-size: 70%; + text-transform: uppercase; } + +.ellaWelcomeDiv > h1 { + text-align: center; + font-weight: 300; } + +.matiasPageContainer { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; } + +.matiasLoadingSpan { + margin: auto; } + +.flexDiv { + display: flex; + flex-direction: row; } + +.matiasHeaderDiv { + width: 100%; + height: 80px; + background-color: #3b34c4; + color: white; + position: relative; } + .matiasHeaderDiv > span:nth-child(2) { + margin-top: 22px; + margin-right: 35px; + font-weight: 300; + font-size: 10pt; + position: absolute; + right: 0; + border: 1px solid white; + border-radius: 20px; + padding: 8px 20px 8px 20px; } + +.matiasUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; } + +.matiasUsername { + float: left; + margin: 30px 0 0 55px; + font-weight: 300; + font-size: 90%; + text-decoration: none; } + +.matiasWelcomeDiv { + background-color: #3b34c4; + color: white; } + +.matiasWelcomeDiv > h1 { + margin-left: 55px; + font-weight: 300; } + +.tutorialPageContainer h1 { + font-weight: 300; + font-size: 2em; + margin-bottom: 1rem; } + .tutorialPageContainer h1:first-of-type { + margin-top: 0; } + .tutorialPageContainer h1:not(:first-of-type) { + margin-top: 3rem; } + +.tutorialPageContainer #contents { + position: fixed; + left: 0px; + top: 0px; + padding-top: 75px; + padding-right: 50px; + padding-left: 50px; + height: 100%; + width: 350px; + box-sizing: border-box; + background-color: #FDFDFD; + font-size: 10pt; + overflow: auto; } + .tutorialPageContainer #contents ul { + padding: 0px; + list-style-type: none; } + .tutorialPageContainer #contents ul li { + padding: 15px 0; + font-weight: 600; + position: relative; } + .tutorialPageContainer #contents ul li ul li { + font-weight: 400; + margin-left: 25px; + position: relative; } + .tutorialPageContainer #contents ul li ul li:hover::after { + content: ''; + background-color: rgba(63, 85, 175, 0.3); + position: absolute; + width: calc(100% + 125px); + height: 100%; + left: -75px; + top: 0px; + pointer-events: none; } + .tutorialPageContainer #contents ul li ul li:first-of-type { + margin-top: 10px; } + .tutorialPageContainer #contents ul li.top-level > a:hover::after { + content: ''; + background-color: rgba(63, 85, 175, 0.3); + position: absolute; + width: calc(100% + 100px); + height: 10pt; + left: -50px; + top: 0px; + padding: 15px 0 21px 0; + pointer-events: none; } + .tutorialPageContainer #contents ul li.top-level.parent + .top-level { + margin-top: -15px; } + .tutorialPageContainer #contents a { + color: #19273C; + text-decoration: none; + display: inline-block; + width: 100%; } + .tutorialPageContainer #contents a:visited { + color: #19273C; + text-decoration: none; } + .tutorialPageContainer #contents a:link { + color: #19273C; + text-decoration: none; } + +.tutorialPageContainer #tutorialContainer { + position: absolute; + top: 75px; + width: 750px; + left: 350px; + padding: 0px 100px; + border-bottom: 50px solid #f5f6fa; + line-height: 1.6; + font-size: 16px; } + .tutorialPageContainer #tutorialContainer h2 { + font-weight: 300; + font-size: 1.35em; + border-bottom: 1px solid #c6c6c6; + padding-bottom: 1rem; + margin-top: 2rem; + margin-bottom: 0.83em; } + .tutorialPageContainer #tutorialContainer h2:first-of-type { + margin-top: 0; } + .tutorialPageContainer #tutorialContainer p, .tutorialPageContainer #tutorialContainer li { + font-size: 10pt; } + .tutorialPageContainer #tutorialContainer ul { + padding-left: 0; } + .tutorialPageContainer #tutorialContainer ul li { + list-style-type: none; + margin-bottom: 10pt; } + .tutorialPageContainer #tutorialContainer .code { + background-color: #FFF; + font-family: "Source Code Pro", monospace; + font-size: 0.8rem; + padding: 1px 4px; + border: 1px solid #E3ECEC; + position: relative; + top: -1px; + display: inline-block; } + +body { + background-color: #F9F9F9; + color: #19273C; } + +.Page { + height: 100vh; + width: 100%; } + +.Username { + float: right; + margin: 30px; } + +body { + background-color: #f5f6fa; } + +.LCcontainer { + background-color: #f5f6fa; + width: 100%; + height: 100vh; + position: relative; } + .LCcontainer > br { + clear: left; } + +.waitText { + position: absolute; + margin-left: 47.5%; + margin-top: 28%; + color: black; } + +.LCHeader { + height: 50px; + background-color: white; + border-bottom: 1px solid #ebeef5; } + .LCHeader > div { + position: relative; + height: 100%; + width: 50px; + border-right: 1px solid #ebeef5; } + .LCHeader > p { + font-size: 10pt; + line-height: 25px; } + +.loc-text { + position: absolute; + top: 0; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + text-align: center; } + +.username-txt { + position: absolute; + top: 0; + right: 0; + text-align: center; + margin-right: 70px; } + +.cardsContainer { + width: 60%; } + .cardsContainer > div > span { + margin-bottom: 10px; } + +.contentTable { + height: 1px; + max-height: 500px; + margin: 20px; + margin-left: 200px; + position: relative; + left: 50%; + margin-left: -597.5px; + top: 15px; } + +tr:nth-child(1) td:not(.blockchainCell) { + min-width: 275px; } + +td > h1 { + font-weight: 300; } + +td { + vertical-align: top; + padding: 12.5px; } + +.blockchainCell { + width: 250px; + height: 100%; } + +.actions { + padding-right: 15px; + display: flex; + justify-content: flex-end; } + .actions > button { + border-radius: 30px; + padding: 10px 20px; + margin-right: 10px; + font-weight: 500; + white-space: nowrap; + text-align: center; + z-index: 99999999; } + .actions > button:enabled { + background-color: #4880ff; + color: #fff; + border: 1px solid #4880ff; } + .actions > button:disabled { + cursor: not-allowed; + background-color: transparent; + border-color: black; + color: black; + opacity: 0.3; } + .actions > button:enabled:hover { + border: 1px solid #4880ff; + background-color: transparent; + color: #4880ff; } + +.statusMessage { + margin: 2.5%; } + +.backButton { + cursor: pointer; + height: 15px; + position: absolute; + top: 0; + bottom: 0; + margin: auto; + left: 50%; + margin-left: -7.5px; + z-index: 999999999; } + +.userInfo { + display: inline-block; + padding: 2% 0 0 2%; } + +.header { + display: flex; } + +.stepper { + width: 50%; + margin: 0 auto; } + +.alertContainer { + margin-top: -12px; + font-size: 10pt; } + +.invisible { + opacity: 0; + cursor: default; } + +.tick { + margin: auto; + width: 0px; + height: 0px; + border-style: solid; + border-width: 0 6px 10px 6px; + border-color: transparent transparent #4d4f75 transparent; + z-index: 100; } + +.alert { + display: flex; + margin: auto; + height: 35px; + width: 300px; + line-height: 1; + border-radius: 5px; + box-shadow: 3px 6px 10px rgba(0, 0, 0, 0.15); + background-color: #4d4f75; } + .alert > p { + color: white; + font-weight: 300; + font-size: small; + margin: auto; + margin-left: 7.5px; + margin-right: 5px; + font-size: 10pt; } + .alert > p > b { + font-weight: 500; } + .alert > button { + padding: 0; + padding-bottom: 3px; + height: 25px; + width: 25px; + margin: auto; + margin-left: 0px; + background: none; + border: none; + color: white; + font-size: large; } + .alert > button:disabled { + cursor: default; } + +.Block { + padding-left: 5px; + align-items: center; + display: flex; + margin: 0; + color: #949494; + min-height: 65px; } + .Block:nth-child(2) .BlockLine { + box-shadow: 0 0 5px #8bb0ff; } + +.BlockLine { + background-color: #477fff; + border-radius: 2px; + width: 2px; + height: 50px; + margin: 0 15px; } + +.BlockText { + display: inline-block; + line-height: 5px; + font-size: 9pt; } + +.BlockNumber { + display: inline-block; } + +.BlockChainDisplay { + overflow-y: auto; + display: flex; + flex-direction: column-reverse; + width: 200px; + height: 0px; + min-height: 100%; } + +.greyBlock { + padding-left: 5px; + padding-top: 10px; + align-items: center; + margin: 0; } + +.greyBlockLine { + background-color: #ddd; + box-shadow: 0 0 5px #d1d1d1; + width: 2px; + height: 25px; + display: inline-block; + margin: 0 15px; } + +.greyBlockNumber { + display: inline-block; + opacity: 0; } + +.outerDiv { + position: relative; } + +.cardContainer { + background-color: white; + width: 215px; + padding: 20px; + border-radius: 4px; + display: flex; + flex-direction: column; + border: 1px solid #e4e4e4; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.05); } + .cardContainer input { + width: 99%; + height: 13px; } + .cardContainer > ul { + margin: 0; + padding: 0; + padding-left: 20px; } + .cardContainer > ul > li { + font-size: 10pt; + margin: 10px 0px; } + .cardContainer > ul > li > input { + width: 99%; + height: 10px; } + .cardContainer > h5 { + margin-top: 0; + margin-right: 0; + margin-left: 0; + margin-bottom: 11px; + font-weight: 600; } + +.rules > div { + width: unset; } + +.editButton { + width: 130px; + height: 34px; + position: absolute; + bottom: -15px; + left: 20px; + border-radius: 30px; + background-color: #4880ff; + border: none; + color: white; + font-size: 10pt; } + .editButton > span { + background-color: #416dd1; + border-radius: 30px; + width: 50px; + float: left; + padding: 3px; } + +.right { + float: right; } + +.disabled { + background: transparent; + box-shadow: none; + border: 1px solid #d1d1d1; } + +.subheadingSpan { + float: left; + clear: left; } + +.topHeading { + float: left; + clear: left; + color: #95989a; + padding-top: 5px; } + +.subheadingSpan, .topHeading { + font-size: 10pt; + padding-bottom: 5px; + width: unset; } + +.LoCCard { + padding: 20px 10px 5px 35px; + margin-left: 60px; + border: 1px solid white; + border-radius: 2px; + text-align: left; + color: white; + font-weight: 300; + min-width: 250px; + max-width: 250px; } + .LoCCard > h2 { + font-weight: 300; + font-size: 10pt; } + .LoCCard > h2 { + font-weight: 300; + font-size: 19px; } + .LoCCard > div { + font-size: 10pt; } + .LoCCard > div > h2 { + font-weight: 300; + font-size: 10pt; } + .LoCCard > div > h2:first-of-type { + font-weight: 300; + font-size: 19px; } + .LoCCard > div > p > b { + font-weight: 500; } + +.noBorder { + border: none; + padding-left: 0; } + .noBorder > p { + font-size: 10pt; + line-height: 1.8; } + +.viewButton { + margin-top: 5px; + margin-left: -5px; + color: white; + padding-top: 12px; + padding-bottom: 12px; + padding-left: 0px; + display: flex; + font-size: 10pt; + background: none; + border: none; } + +.applyButton { + background-color: #fc6a66; + border: 1px solid #fc6a66; + padding: 8px 20px; + margin-top: 23px; + border-radius: 20px; } + +.buttonText:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; } + +.buttonText { + margin: 0; } + .buttonText > span { + border-bottom: 1px solid white; } + +.LoCCardBob { + padding: 8px 20px 30px 30px; + margin-left: 40px; + margin-top: 8px; + border: 5px solid #7646c0; + text-align: left; + color: white; + font-weight: 300; + min-width: 220px; + font-size: 10pt; + height: 180px; } + .LoCCardBob > div > h2 { + font-weight: 300; + font-size: 10pt; } + .LoCCardBob > div > h2:first-of-type { + font-weight: 300; + font-size: 19px; } + .LoCCardBob > div > b { + font-weight: 500; } + .LoCCardBob > div > p > b { + font-weight: 500; } + .LoCCardBob > .background { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } + +.viewButtonBob { + margin-top: 5px; + margin-left: -5px; + padding-top: 12px; + padding-bottom: 12px; + float: right; + width: 15px; + transform: rotate(180deg); } + +.viewButtonBob:hover { + cursor: pointer; } + +.viewButtonImage { + padding-right: 8px; + padding-left: 8px; } + +.toggleContainer { + display: flex; + margin-top: 25px; } + .toggleContainer.hide { + opacity: 0; } + +.shipText { + margin-left: 10px; + margin-top: 6px; } + +#LoCCardBobAccepted { + background: linear-gradient(90deg, #7a3cbc 0%, #ae4581 100%); + border: none; + padding: 13px 25px 35px 35px; + box-shadow: none; } + +.customToggle .react-toggle-track { + height: 20px; + margin-top: 5px; + background-color: #1b1b28 !important; + box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.5); } + +.customToggle .react-toggle-thumb { + height: 28px; + width: 28px; + left: 0; + border: 2px solid #dcdcdc; + background-color: #f4f4f4; } + +.customToggle .react-toggle-thumb:after { + content: "\D7"; + color: #9b9b9b; + font-weight: 500; + font-size: 13pt; + width: 0px; + height: 0px; + margin-left: 6.5px; } + +.customToggle .react-toggle-checked { + left: 20px; } + +.customToggle .react-toggle-track-check { + display: none; } + +.customToggle .react-toggle-track-x { + display: none; } + +.customToggle.react-toggle--disabled { + opacity: 1; } + +.customToggle.react-toggle--checked .react-toggle-track { + background-color: #9b65c3 !important; } + +.customToggle.react-toggle--checked .react-toggle-thumb { + background-color: #953ea5 !important; + border-color: #953ea5; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.15); } + +.customToggle.react-toggle--checked .react-toggle-thumb:after { + content: ""; + height: 5px; + width: 8px; + border-left: 2px solid #FFFFFF; + border-bottom: 2px solid #FFFFFF; + transform: rotate(-45deg); + display: inline-block; } + +.customToggleAlice .react-toggle-track { + background-color: #d6e1f6 !important; } + +.customToggleAlice.react-toggle--checked .react-toggle-track { + background-color: #d6e1f6 !important; } + +.customToggleAlice.react-toggle--checked .react-toggle-thumb { + background-color: #6464e8 !important; + border-color: #6464e8; } + +.background { + display: flex; + align-items: center; + width: 100%; + height: 100%; + position: fixed; + background-color: rgba(204, 205, 206, 0.6); + z-index: 100000000; } + +.container { + flex-direction: column; + display: flex; + margin: auto; + height: 300px; + width: 300px; + background-color: white; + justify-content: space-between; } + .container > div { + height: 100%; + position: relative; } + .container > div > h4 { + text-align: left; + color: #2e3e4f; + margin-left: 25px; } + +.shipContainer { + height: 195px; + width: 500px; } + +.title { + color: black; } + +.message { + color: black; } + +.textMargins { + margin: 30px; } + +.buttonsRow { + position: absolute; + bottom: 20px; + right: 30px; + display: inline; + vertical-align: middle; } + .buttonsRow > button:first-of-type { + margin-left: 10px; } + +.shipButtonsRow { + position: absolute; + bottom: 15px; + right: 20px; + display: inline; + vertical-align: middle; } + .shipButtonsRow > button:first-of-type { + margin-left: 10px; } + +.yesButton { + border-radius: 30px; + font-size: 85%; + white-space: nowrap; + text-align: center; + float: right; } + +.yesButton:enabled { + background-color: #4880ff; + color: #fff; + border: 1px solid #4880ff; } + +.yesButton:disabled { + cursor: not-allowed; } + +.yesButton:hover:enabled { + border: 1px solid #4880ff; + color: #4880ff; + background-color: transparent; } + +.cancelButton { + padding-top: 2.5px; + border: none; + background: none; + font-size: 85%; + text-decoration: underline; + color: #4880ff; + float: right; + vertical-align: middle; } + +.files-table { + margin: 0 0 0 25px; + width: 90%; } + .files-table td { + padding: 10px 10px 10px 0; + font-weight: normal; + color: #2e3e4f; } + +.loadingMessage { + color: black; + text-align: left; + padding-left: 25px; } + +.checkboxContainer input { + visibility: hidden; + /* <-- hide the default checkbox, the rest is to hide and alllow tabbing, which display:none prevents */ + display: block; + height: 0; + width: 0; + position: absolute; + overflow: hidden; } + +.checkboxContainer span { + /* <-- style the artificial checkbox */ + height: 10px; + width: 10px; + box-shadow: 0 0 0 2px #fff, 0 0 0 3px #ccc; + display: inline-block; + border-radius: 50px; + cursor: pointer; } + +[type=checkbox]:checked + span { + /* <-- style its checked state */ + background: #4880ff; } + +.checkmark { + margin-right: 8px; } + +.UserDetails { + font-size: 10pt; + text-align: left; + padding-left: 60px; + padding-bottom: 50px; + color: white; + font-weight: 300; } + .UserDetails > h2 { + font-size: 19px; + font-weight: 300; } + .UserDetails > div { + margin-bottom: 10px; } + .UserDetails > div > b { + font-weight: 500; } + +.userDetailsTextBold { + font-weight: bold; + display: inline; } + +.ellaTable > .headerBar { + border: 1px solid #e8e9eb; + background-color: #f6f8fb; + line-height: 3; } + .ellaTable > .headerBar > .locOrdersText { + margin-left: 55px; } + +.ellaTable > .bankTable { + width: 100%; + margin: 30px 0; + border-collapse: collapse; } + .ellaTable > .bankTable > tbody > tr:not(:first-of-type):hover { + cursor: pointer; + background-color: #f6f8fb; + outline-color: #e8e9eb; } + .ellaTable > .bankTable > tbody > tr:not(:first-of-type):hover > td { + color: #a0066f; } + .ellaTable > .bankTable > tbody > tr { + outline: 1px solid transparent; } + .ellaTable > .bankTable > tbody > tr > th { + text-align: left; + height: 50px; } + .ellaTable > .bankTable > tbody > tr > th:nth-child(1) { + padding-left: 55px; } + .ellaTable > .bankTable > tbody > tr > td { + padding-left: 0; + font-weight: 500; + font-size: 12pt; + color: #999; } + .ellaTable > .bankTable > tbody > tr > td:nth-child(1) { + font-weight: 500; + text-decoration: underline; + padding-left: 55px; } + +.matiasTable > .headerBar { + border: 1px solid #e8e9eb; + background-color: #f6f8fb; + line-height: 3; } + .matiasTable > .headerBar > .locOrdersText { + margin-left: 55px; } + +.matiasTable > .bankTable { + width: 100%; + margin: 30px 0; + border-collapse: collapse; } + .matiasTable > .bankTable > tbody > tr:not(:first-of-type):hover { + cursor: pointer; + background-color: #f6f8fb; + outline-color: #e8e9eb; } + .matiasTable > .bankTable > tbody > tr:not(:first-of-type):hover > td { + color: #3b34c4; } + .matiasTable > .bankTable > tbody > tr { + outline: 1px solid transparent; } + .matiasTable > .bankTable > tbody > tr > th { + text-align: left; + height: 50px; } + .matiasTable > .bankTable > tbody > tr > th:nth-child(1) { + padding-left: 55px; } + .matiasTable > .bankTable > tbody > tr > td { + padding-left: 0; + font-weight: 500; + font-size: 12pt; + color: #999; } + .matiasTable > .bankTable > tbody > tr > td:nth-child(1) { + font-weight: 500; + text-decoration: underline; + padding-left: 55px; } + +.stepContainer { + position: relative; } + .stepContainer:first-child .stepInfo { + left: -62px; } + +.activeStep { + box-sizing: border-box; + border: 6px solid #ffffff; + background-color: #477fff; + height: 20px; + width: 20px; + border-radius: 50%; + z-index: 1; + box-shadow: 0 0px 10px #989898; + z-index: 2; } + +.completedStep { + background-color: #477fff; + height: 20px; + width: 20px; + border-radius: 50%; + box-shadow: 0 0px 4px #989898; + z-index: 2; + border: 3px solid transparent; + box-sizing: border-box; } + +.uncompletedStep { + box-sizing: border-box; + border: 3px solid #e4e4e4; + height: 20px; + width: 20px; + border-radius: 50%; + z-index: 2; } + +.stepInfo { + height: 70px; + width: 145px; + margin: 0 auto; + z-index: 10; + text-align: center; + position: absolute; + top: -55px; + left: -35px; } + +.stepInfoMessage { + position: relative; + border: 2px solid #e4e4e4; + background-color: #ffffff; + height: 55px; + width: 145px; + margin-bottom: 30px; + margin: 0 auto; + text-align: center; + box-shadow: 0 0 20px rgba(220, 220, 220, 0.5); + border-radius: 4px; + z-index: 1; + line-height: 25px; } + +.stepInfoTick { + position: relative; + bottom: 2px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 10px solid #ffffff; + margin-left: 66.5px; + margin-bottom: 26px; + z-index: 2; } + +.nodeContainer { + display: flex; + align-items: center; + position: relative; + top: 14px; } + +.stepConnector { + height: 3px; + background-color: #e4e4e4; + width: 27px; + z-index: 1; } + +.blankStepConnector { + display: none; } + +.blankStepInfo { + display: none; } + +.stepNumberText { + position: relative; + top: 3px; + display: inline; + color: #c7c7c7; + font-family: 'Open Sans'; + font-size: 10pt; } + +.stepMessageText { + display: inline; + font-family: 'Open Sans'; + font-size: 10pt; } + +.Stepper { + display: flex; + margin-top: 20px; } + +body { + font-family: 'Open Sans'; } + +button { + font-family: 'Open Sans'; } diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_alert.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_alert.scss new file mode 100644 index 00000000..9df38bb0 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_alert.scss @@ -0,0 +1,64 @@ +.alertContainer { + margin-top: -12px; + font-size: 10pt; +} + +.invisible { + opacity: 0; + cursor: default; +} + +.tick { + margin: auto; + width: 0px; + height: 0px; + border-style: solid; + border-width: 0 6px 10px 6px; + border-color: transparent transparent #4d4f75 transparent; + z-index: 100; +} + +.alert { + display: flex; + margin: auto; + height: 35px; + width: 300px; + line-height: 1; + border-radius: 5px; + box-shadow: 3px 6px 10px rgba(0, 0, 0, 0.15); + background-color: #4d4f75; + + > p { + color: white; + font-weight: 300; + font-size: small; + margin: auto; + margin-left: 7.5px; + margin-right: 5px; + font-size: 10pt; + + > b { + font-weight: 500; + } + + } + + > button { + padding: 0; + padding-bottom: 3px; + height: 25px; + width: 25px; + margin: auto; + margin-left: 0px; + background: none; + border: none; + color: white; + font-size: large; + + } + + > button:disabled { + cursor: default; + } + +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_all.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_all.scss new file mode 100644 index 00000000..11c71b84 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_all.scss @@ -0,0 +1,10 @@ +@import 'alert'; +@import 'block'; +@import 'blockchaindisplay'; +@import 'detailscard'; +@import 'loccard'; +@import 'modal'; +@import 'userdetails'; +@import 'table'; +@import 'step'; +@import 'stepper'; diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_block.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_block.scss new file mode 100644 index 00000000..c436db29 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_block.scss @@ -0,0 +1,33 @@ +.Block { + padding-left: 5px; + align-items: center; + display: flex; + margin: 0; + color: #949494; + min-height: 65px; + + &:nth-child(2) { + .BlockLine { + box-shadow: 0 0 5px #8bb0ff; + } + + } +} + +.BlockLine { + background-color: #477fff; + border-radius: 2px; + width: 2px; + height: 50px; + margin: 0 15px; +} + +.BlockText { + display: inline-block; + line-height: 5px; + font-size: 9pt; +} + +.BlockNumber { + display: inline-block; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_blockchaindisplay.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_blockchaindisplay.scss new file mode 100644 index 00000000..57d2c0b6 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_blockchaindisplay.scss @@ -0,0 +1,29 @@ +.BlockChainDisplay { + overflow-y: auto; + display: flex; + flex-direction: column-reverse; + width: 200px; + height: 0px; + min-height: 100%; +} + +.greyBlock { + padding-left: 5px; + padding-top: 10px; + align-items: center; + margin: 0; +} + +.greyBlockLine { + background-color: #ddd; + box-shadow: 0 0 5px #d1d1d1; + width: 2px; + height: 25px; + display: inline-block; + margin: 0 15px; +} + +.greyBlockNumber { + display: inline-block; + opacity: 0; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_detailscard.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_detailscard.scss new file mode 100644 index 00000000..a4fc19b3 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_detailscard.scss @@ -0,0 +1,98 @@ +.outerDiv { + position: relative; +} + +.cardContainer { + background-color: white; + width: 215px; + padding: 20px; + border-radius: 4px; + display: flex; + flex-direction: column; + border: 1px solid #e4e4e4; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.05); + + input { + width: 99%; + height: 13px; + } + + > ul { + margin: 0; + padding: 0; + padding-left: 20px; + + > li { + font-size: 10pt; + margin: 10px 0px; + + > input { + width: 99%; + height: 10px; + } + } + } + + > h5 { + margin-top: 0; + margin-right: 0; + margin-left: 0; + margin-bottom: 11px; + font-weight: 600; + } +} + +.rules { + > div { + width: unset; + } +} + +.editButton { + width: 130px; + height: 34px; + position: absolute; + bottom: -15px; + left: 20px; + border-radius: 30px; + background-color: #4880ff; + border: none; + color: white; + font-size: 10pt; + + > span { + background-color: #416dd1;; + border-radius: 30px; + width: 50px; + float: left; + padding: 3px; + } +} + +.right { + float: right; +} + +.disabled { + background: transparent; + box-shadow: none; + border: 1px solid #d1d1d1; +} + +.subheadingSpan { + float: left; + clear: left; +} + +.topHeading { + float: left; + clear: left; + color: #95989a; + padding-top: 5px; +} + +.subheadingSpan, .topHeading { + font-size: 10pt; + padding-bottom: 5px; + width: unset; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_loccard.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_loccard.scss new file mode 100644 index 00000000..a082c4c8 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_loccard.scss @@ -0,0 +1,238 @@ +.LoCCard { + padding: 20px 10px 5px 35px; + margin-left: 60px; + border: 1px solid white; + border-radius: 2px; + text-align: left; + color: white; + font-weight: 300; + min-width: 250px; + max-width: 250px; + + > h2 { + font-weight: 300; + font-size: 10pt; + } + > h2 { + font-weight: 300; + font-size: 19px; + } + > div { + font-size: 10pt; + > h2 { + font-weight: 300; + font-size: 10pt; + } + > h2:first-of-type { + font-weight: 300; + font-size: 19px; + } + > p > b { + font-weight: 500; + } + } +} + +.noBorder { + border: none; + padding-left: 0; + + > p { + font-size: 10pt; + line-height: 1.8; + } + +} + +.viewButton { + margin-top: 5px; + margin-left: -5px; + color: white; + padding-top: 12px; + padding-bottom: 12px; + padding-left: 0px; + display: flex; + font-size: 10pt; + background: none; + border: none; +} + +.applyButton { + background-color: #fc6a66; + border: 1px solid #fc6a66; + padding: 8px 20px; + margin-top: 23px; + border-radius: 20px; +} + +.buttonText:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; +} + +.buttonText { + margin: 0; + + > span { + border-bottom: 1px solid white; + } +} + +.LoCCardBob { + padding: 8px 20px 30px 30px; + margin-left: 40px; + margin-top: 8px; + border: 5px solid #7646c0; + text-align: left; + color: white; + font-weight: 300; + min-width: 220px; + font-size: 10pt; + height: 180px; + + > div { + > h2 { + font-weight: 300; + font-size: 10pt; + } + > h2:first-of-type { + font-weight: 300; + font-size: 19px; + } + > b { + font-weight:500; + } + > p > b { + font-weight: 500; + } + } + + > .background { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} + +.viewButtonBob { + margin-top: 5px; + margin-left: -5px; + padding-top: 12px; + padding-bottom: 12px; + float: right; + width: 15px; + transform: rotate(180deg); +} + +.viewButtonBob:hover { + cursor: pointer; +} + +.viewButtonImage { + padding-right: 8px; + padding-left: 8px; +} + +.toggleContainer { + display: flex; + margin-top: 25px; + + &.hide { + opacity: 0; + } +} + +.shipText { + margin-left: 10px; + margin-top: 6px +} + +#LoCCardBobAccepted { + background: linear-gradient(90deg, #7a3cbc 0%, #ae4581 100%); + border: none; + padding: 13px 25px 35px 35px; + box-shadow: none; +} + +// CUSTOM TOGGLE CLASSES // +.customToggle .react-toggle-track { + height: 20px; + margin-top: 5px; + background-color: #1b1b28 !important; + box-shadow: inset 0px 0px 5px rgba(0, 0, 0, 0.5); +} + +.customToggle .react-toggle-thumb { + height: 28px; + width: 28px; + left: 0; + border: 2px solid #dcdcdc; + background-color: #f4f4f4; +} + +.customToggle .react-toggle-thumb:after { + content: "\D7"; + color: #9b9b9b; + font-weight: 500; + font-size: 13pt; + width: 0px; + height: 0px; + margin-left: 6.5px; +} + +.customToggle .react-toggle-checked { + left: 20px; + +} + +.customToggle .react-toggle-track-check { + display: none; +} + +.customToggle .react-toggle-track-x { + display: none; +} + +.customToggle.react-toggle--disabled { + opacity: 1; +} + +.customToggle.react-toggle--checked .react-toggle-track { + background-color: #9b65c3 !important; +} + +.customToggle.react-toggle--checked .react-toggle-thumb { + background-color: #953ea5 !important; + border-color: #953ea5; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.15); +} + +.customToggle.react-toggle--checked .react-toggle-thumb:after { + content: ""; + height: 5px; + width: 8px; + border-left: 2px solid #FFFFFF; + border-bottom: 2px solid #FFFFFF; + transform: rotate(-45deg); + display: inline-block; +} + +.customToggleAlice .react-toggle-track { + background-color: #d6e1f6 !important; +} + +.customToggleAlice.react-toggle--checked .react-toggle-track { + background-color: #d6e1f6 !important; +} + +.customToggleAlice.react-toggle--checked .react-toggle-thumb { + background-color: #6464e8 !important; + border-color: #6464e8; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_modal.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_modal.scss new file mode 100644 index 00000000..fbb2c059 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_modal.scss @@ -0,0 +1,151 @@ +.background { + display: flex; + align-items: center; + width:100%; + height:100%; + position: fixed; + background-color: rgba(204, 205, 206, 0.6); + z-index: 100000000; +} + +.container { + flex-direction: column; + display: flex; + margin: auto; + height: 300px; + width: 300px; + background-color: white; + justify-content: space-between; + + > div { + height: 100%; + position: relative; + + > h4 { + text-align: left; + color: #2e3e4f; + margin-left: 25px; + } + } +} + +.shipContainer { + height: 195px; + width: 500px; +} + +.title { + color: black; +} + +.message { + color: black; +} + +.textMargins { + margin: 30px; +} + +.buttonsRow { + position: absolute; + bottom: 20px; + right: 30px; + display: inline; + vertical-align: middle; + + > button:first-of-type { + margin-left: 10px; + } + +} + +.shipButtonsRow { + position: absolute; + bottom: 15px; + right: 20px; + display: inline; + vertical-align: middle; + + > button:first-of-type { + margin-left: 10px; + } +} + +.yesButton { + border-radius: 30px; + font-size: 85%; + white-space: nowrap; + text-align: center; + float: right; +} + +.yesButton:enabled { + background-color: #4880ff; + color: #fff; + border: 1px solid #4880ff +} + +.yesButton:disabled { + cursor: not-allowed; +} + +.yesButton:hover:enabled { + border: 1px solid #4880ff; + color: #4880ff; + background-color: transparent; +} + +.cancelButton { + padding-top: 2.5px; + border: none; + background: none; + font-size: 85%; + text-decoration: underline; + color: #4880ff; + float: right; + vertical-align: middle; +} + +.files-table { + margin: 0 0 0 25px; + width: 90%; + + td { + padding: 10px 10px 10px 0; + font-weight: normal; + color: #2e3e4f; + } +} + +.loadingMessage { + color: black; + text-align: left; + padding-left: 25px; +} + +.checkboxContainer input { + visibility: hidden;/* <-- hide the default checkbox, the rest is to hide and alllow tabbing, which display:none prevents */ + display:block; + height:0; + width:0; + position:absolute; + overflow:hidden; +} +.checkboxContainer span {/* <-- style the artificial checkbox */ + height: 10px; + width: 10px; + box-shadow: + 0 0 0 2px #fff, + 0 0 0 3px #ccc; + display: inline-block; + border-radius: 50px; + cursor: pointer; +} + +[type=checkbox]:checked + span {/* <-- style its checked state */ + background: #4880ff; +} + +.checkmark { + margin-right: 8px; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_step.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_step.scss new file mode 100644 index 00000000..cbcb5044 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_step.scss @@ -0,0 +1,114 @@ +.stepContainer{ + position: relative; + &:first-child { + .stepInfo { + left: -62px; + } + } +} + +.activeStep { + box-sizing: border-box; + border:6px solid #ffffff; + background-color: #477fff; + height: 20px; + width: 20px; + border-radius: 50%; + z-index: 1; + box-shadow:0 0px 10px #989898; + z-index: 2; +} +.completedStep{ + background-color: #477fff; + height: 20px; + width: 20px; + border-radius: 50%; + box-shadow:0 0px 4px #989898; + z-index: 2; + border: 3px solid transparent; + box-sizing: border-box; +} +.uncompletedStep{ + box-sizing: border-box; + border: 3px solid #e4e4e4; + height: 20px; + width: 20px; + border-radius: 50%; + z-index: 2; +} + +.stepInfo{ + height: 70px; + width: 145px; + margin: 0 auto; + z-index: 10; + text-align: center; + position: absolute; + top: -55px; + left: -35px; +} + +.stepInfoMessage{ + position: relative; + border:2px solid #e4e4e4; + background-color: #ffffff; + height: 55px; + width: 145px; + margin-bottom: 30px; + margin: 0 auto; + text-align: center; + box-shadow:0 0 20px rgba(220,220,220, 0.5); + border-radius: 4px; + z-index: 1; + line-height: 25px; +} + +.stepInfoTick{ + position: relative; + bottom: 2px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 10px solid #ffffff; + margin-left: 66.5px; + margin-bottom: 26px; + z-index: 2; +} + +.nodeContainer{ + display: flex; + align-items: center; + position: relative; + top: 14px; +} + +.stepConnector{ + height: 3px; + background-color: #e4e4e4; + width: 27px; + z-index: 1; +} + +.blankStepConnector{ + display: none; +} + +.blankStepInfo{ + display: none; +} + +.stepNumberText{ + position: relative; + top: 3px; + display: inline; + color: #c7c7c7; + font-family: 'Open Sans'; + font-size: 10pt; +} + +.stepMessageText{ + display: inline; + font-family: 'Open Sans'; + font-size: 10pt; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_stepper.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_stepper.scss new file mode 100644 index 00000000..516fecac --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_stepper.scss @@ -0,0 +1,4 @@ +.Stepper { + display: flex; + margin-top: 20px; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_table.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_table.scss new file mode 100644 index 00000000..8d6906f2 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_table.scss @@ -0,0 +1,117 @@ +.ellaTable { + + > .headerBar { + border: 1px solid #e8e9eb; + background-color: #f6f8fb; + line-height: 3; + + > .locOrdersText { + margin-left: 55px; + } + } + + > .bankTable { + width: 100%; + margin: 30px 0; + border-collapse: collapse; + + > tbody { + + > tr:not(:first-of-type):hover { + cursor: pointer; + background-color: #f6f8fb; + outline-color: #e8e9eb; + + > td { + color: #a0066f; + } + } + + > tr { + + outline: 1px solid transparent; + + > th { + text-align: left; + height: 50px; + } + + > th:nth-child(1) { + padding-left: 55px; + } + + > td { + padding-left: 0; + font-weight: 500; + font-size: 12pt; + color: #999; + } + + > td:nth-child(1) { + font-weight: 500; + text-decoration: underline; + padding-left: 55px; + } + } + } + } +} + +.matiasTable { + + > .headerBar { + border: 1px solid #e8e9eb; + background-color: #f6f8fb; + line-height: 3; + + > .locOrdersText { + margin-left: 55px; + } + } + + > .bankTable { + width: 100%; + margin: 30px 0; + border-collapse: collapse; + + > tbody { + + > tr:not(:first-of-type):hover { + cursor: pointer; + background-color: #f6f8fb; + outline-color: #e8e9eb; + + > td { + color: #3b34c4; + } + } + + > tr { + + outline: 1px solid transparent; + + > th { + text-align: left; + height: 50px; + } + + > th:nth-child(1) { + padding-left: 55px; + } + + > td { + padding-left: 0; + font-weight: 500; + font-size: 12pt; + color: #999; + } + + > td:nth-child(1) { + font-weight: 500; + text-decoration: underline; + padding-left: 55px; + } + } + } + } +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/components/_userdetails.scss b/packages/letters-of-credit/src/stylesheets/sass/components/_userdetails.scss new file mode 100644 index 00000000..84e28c78 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/components/_userdetails.scss @@ -0,0 +1,25 @@ +.UserDetails { + font-size: 10pt; + text-align: left; + padding-left: 60px; + padding-bottom: 50px; + color: white; + font-weight: 300; + > h2 { + font-size: 19px; + font-weight: 300; + } + + > div { + margin-bottom: 10px; + + > b { + font-weight: 500; + } + } +} + +.userDetailsTextBold { + font-weight: bold; + display: inline; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/layout/_all.scss b/packages/letters-of-credit/src/stylesheets/sass/layout/_all.scss new file mode 100644 index 00000000..87e56595 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/layout/_all.scss @@ -0,0 +1 @@ +@import 'letterofcredit'; diff --git a/packages/letters-of-credit/src/stylesheets/sass/layout/_letterofcredit.scss b/packages/letters-of-credit/src/stylesheets/sass/layout/_letterofcredit.scss new file mode 100644 index 00000000..c32e4732 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/layout/_letterofcredit.scss @@ -0,0 +1,164 @@ +body { + background-color: #f5f6fa; +} + +.LCcontainer { + background-color: #f5f6fa; + width: 100%; + height: 100vh; + position: relative; + + > br { + clear: left; + } +} + +.waitText { + position: absolute; + margin-left: 47.5%; + margin-top: 28%; + color: black; +} + +.LCHeader { + height: 50px; + background-color: white; + border-bottom: 1px solid #ebeef5; + + > div { + position: relative; + height: 100%; + width: 50px; + border-right: 1px solid #ebeef5; + } + + > p { + font-size: 10pt; + line-height: 25px; + } +} + +.loc-text { + position: absolute; + top: 0; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + text-align: center; +} + +.username-txt { + position: absolute; + top: 0; + right: 0; + text-align: center; + margin-right: 70px; +} + +.cardsContainer { + width: 60%; + + > div > span { + margin-bottom: 10px; + } +} + +.contentTable { + height: 1px; + max-height: 500px; + margin: 20px; + margin-left: 200px; + position: relative; + left: 50%; + margin-left: -597.5px; + top: 15px; +} + +tr:nth-child(1) { + td { + &:not(.blockchainCell) { + min-width: 275px; + } + } +} + +td > h1 { + font-weight: 300; +} + +td { + vertical-align: top; + padding: 12.5px; +} + +.blockchainCell { + width: 250px; + height: 100%; +} + +.actions { + padding-right: 15px; + display: flex; + justify-content: flex-end; + + > button { + border-radius: 30px; + padding: 10px 20px; + margin-right: 10px; + font-weight: 500; + white-space: nowrap; + text-align: center; + z-index: 99999999; + } + + > button:enabled { + background-color: #4880ff; + color: #fff; + border: 1px solid #4880ff; + } + + > button:disabled { + cursor: not-allowed; + background-color: transparent; + border-color: black; + color: black; + opacity: 0.3; + } + + > button:enabled:hover { + border: 1px solid #4880ff; + background-color: transparent; + color: #4880ff; + } +} + +.statusMessage { + margin: 2.5%; +} + +.backButton { + cursor: pointer; + height: 15px; + position: absolute; + top: 0; + bottom: 0; + margin: auto; + left: 50%; + margin-left: -7.5px; + z-index: 999999999; +} + +.userInfo { + display: inline-block; + padding: 2% 0 0 2%; +} + +.header { + display: flex; +} + +.stepper { + width: 50%; + margin: 0 auto; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/main.scss b/packages/letters-of-credit/src/stylesheets/sass/main.scss new file mode 100644 index 00000000..1021636f --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/main.scss @@ -0,0 +1,12 @@ +@import 'pages/all'; +@import 'layout/all'; +@import 'components/all'; +@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600'); + +body { + font-family: 'Open Sans'; +} + +button { + font-family: 'Open Sans'; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_alicepage.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_alicepage.scss new file mode 100644 index 00000000..904d3460 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_alicepage.scss @@ -0,0 +1,105 @@ +.alicePageContainer { + width: 100%; + height: 100vh; + background-image: url("../../resources/backgrounds/BACKGROUND3.svg"); + background-repeat: no-repeat; + background-color: #3B51DC; + background-size: contain; + display: flex; + flex-direction: column; +} + +.loadingSpan { + margin: auto; + color: white; +} + +.flexDiv { + display: flex; + flex-direction: row; +} + +.aliceHeaderDiv { + width: 100%; + height: 100px; +} + +.aliceUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; +} + +.aliceUsername { + float: left; + margin: 30px; + font-weight: 300; + font-size: 90%; + color: white; + text-decoration: none; +} + +.aliceUsernameIcon { + margin-right: 5px; +} + +.aliceMenuItems { + position: absolute; + right: 0; + margin-top: 30px; + > span { + margin: 30px; + color: white; + font-weight: 300; + font-size: 10pt; + } +} + +.aliceWelcomeDiv { + > h1 { + margin-left: 60px; + margin-top: 45px; + margin-bottom: 40px; + color: white; + } +} + +.aliceWelcomeMessage { + font-weight: 300; + font-size:44px; + color:#ffffff; + letter-spacing:0; +} + +.alertsDiv { + display: flex; + flex-direction: column; + float: right; +} + +.locDiv { + display: flex; + flex-direction: row; + width: 100%; + overflow-x: auto; +} + +.currentBalance { + margin-top: 5px; + margin-left: -5px; + border: 1px solid #273ec6; + border-radius: 20px; + background-color: #273ec6; + color: white; + padding: 8px 20px; +} + +.cardSpace { + display: inline-block; + margin-left: 60px; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_all.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_all.scss new file mode 100644 index 00000000..9d0578d9 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_all.scss @@ -0,0 +1,6 @@ +@import 'alicepage'; +@import 'bobpage'; +@import 'ellapage'; +@import 'matiaspage'; +@import 'tutorialpage'; +@import 'page'; diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_bobpage.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_bobpage.scss new file mode 100644 index 00000000..5e2e2c8b --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_bobpage.scss @@ -0,0 +1,88 @@ +.bobPageContainer { + width: 100%; + height: 100vh; + background-color: #28293c; + display: flex; + flex-direction: column; +} + +.loadingSpan { + margin: auto; + color: white; +} + +.flexDiv { + display: flex; + flex-direction: row; +} + +.bobHeaderDiv { + width: 100%; + height: 10vh; +} + +.bobUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #bc5183; + border-radius: 15px; + background-color: #bc5183; + display: inline-block; + margin-right: 5px; +} + +.bobUsername { + float: left; + margin: 30px; + font-weight: 300; + font-size: 90%; + color: white; + text-decoration: none; +} + +.infoDivBob { + height: 30vh; +} + +.bobWelcomeDiv { + text-align: center; + margin-left: 0; + margin-top: 0; + color: white; + font-size: 20px +} + +.bobDetailsDiv { + margin-top: 1vh; + color: white; +} + +.alertsDiv { + display: flex; + flex-direction: column; +} + +.locDivBob { + display: flex; + flex-direction: row; + width: 100%; + overflow-x: auto; + padding: 10px 0; +} + +.LoCCardConnector { + border: 5px solid #7646c0; + width: 10px; + height: 10px; +} + +#welcomeMessage { + font-size: 20px; + font-weight: 300; +} + +#accountBalance { + font-size: 55px; + font-weight: 300; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_ellapage.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_ellapage.scss new file mode 100644 index 00000000..9bef2b0c --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_ellapage.scss @@ -0,0 +1,58 @@ +.ellaPageContainer { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; +} + +.ellaLoadingSpan { + margin: auto; +} + +.flexDiv { + display: flex; + flex-direction: row; +} + +.ellaHeaderDiv { + background-color: #28293c; + width: 100%; + color: white; +} + +.ellaUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #bc5183; + border-radius: 15px; + background-color: #bc5183; + display: inline-block; + margin-right: 5px; +} + +.ellaUsername { + float: left; + margin: 15px 0 0 30px; + font-weight: 300; + font-size: 90%; +} + +.ellaWelcomeDiv { + background-color: #28293c; + color: white; + text-align: center; + text-shadow: 0 1px #4e3747; + padding-top: 24px; +} + +.ellaWelcomeDiv > span { + font-weight: 500; + font-size: 70%; + text-transform: uppercase; +} + +.ellaWelcomeDiv > h1 { + text-align: center; + font-weight: 300; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_matiaspage.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_matiaspage.scss new file mode 100644 index 00000000..7e36456b --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_matiaspage.scss @@ -0,0 +1,65 @@ +.matiasPageContainer { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; +} + +.matiasLoadingSpan { + margin: auto; +} + +.flexDiv { + display: flex; + flex-direction: row; +} + +.matiasHeaderDiv { + width: 100%; + height: 80px; + background-color: #3b34c4; + color: white; + position: relative; + + > span:nth-child(2) { + margin-top: 22px; + margin-right: 35px; + font-weight: 300; + font-size: 10pt; + position: absolute; + right: 0; + border: 1px solid white; + border-radius: 20px; + padding: 8px 20px 8px 20px; + } + +} + +.matiasUsername:before { + content: ""; + height: 7.5px; + width: 7.5px; + border: 1px solid #fc6a66; + border-radius: 15px; + background-color: #fc6a66; + display: inline-block; + margin-right: 5px; +} + +.matiasUsername { + float: left; + margin: 30px 0 0 55px; + font-weight: 300; + font-size: 90%; + text-decoration: none; +} + +.matiasWelcomeDiv { + background-color: #3b34c4; + color: white; +} + +.matiasWelcomeDiv > h1 { + margin-left: 55px; + font-weight: 300; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_page.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_page.scss new file mode 100644 index 00000000..c4607a54 --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_page.scss @@ -0,0 +1,9 @@ +.Page { + height: 100vh; + width: 100% +} + +.Username { + float: right; + margin: 30px; +} diff --git a/packages/letters-of-credit/src/stylesheets/sass/pages/_tutorialpage.scss b/packages/letters-of-credit/src/stylesheets/sass/pages/_tutorialpage.scss new file mode 100644 index 00000000..bf3b3fae --- /dev/null +++ b/packages/letters-of-credit/src/stylesheets/sass/pages/_tutorialpage.scss @@ -0,0 +1,160 @@ +$contentsWidth: 350px; +$top: 75px; + +.tutorialPageContainer { + + h1 { + font-weight: 300; + font-size: 2em; + margin-bottom: 1rem; + + &:first-of-type { + margin-top: 0; + } + + &:not(:first-of-type) { + margin-top: 3rem; + } + } + + #contents { + position: fixed; + left: 0px; + top: 0px; + padding-top: $top; + padding-right: 50px; + padding-left: 50px; + height: 100%; + width: $contentsWidth; + box-sizing: border-box; + background-color: #FDFDFD; + font-size: 10pt; + overflow: auto; + + ul { + padding: 0px; + list-style-type: none; + + li { + padding: 15px 0; + font-weight: 600; + position: relative; + + ul { + li { + font-weight: 400; + margin-left: 25px; + position: relative; + + &:hover { + &::after { + content: ''; + background-color: rgba(63,85,175,0.3); + position: absolute; + width: calc(100% + 125px); + height: 100%; + left: -75px; + top: 0px; + pointer-events: none; + } + } + + &:first-of-type { + margin-top: 10px; + } + } + } + + &.top-level { + & >a:hover { + &::after { + content: ''; + background-color: rgba(63,85,175,0.3); + position: absolute; + width: calc(100% + 100px); + height: 10pt; + left: -50px; + top: 0px; + padding: 15px 0 21px 0; + pointer-events: none; + } + } + + &.parent { + + + .top-level { + margin-top: -15px; + } + } + } + } + } + + a { + color: #19273C; + text-decoration: none; + display: inline-block; + width: 100%; + } + a:visited { + color: #19273C; + text-decoration: none; + } + a:link { + color: #19273C; + text-decoration: none; + } + } + + #tutorialContainer { + position: absolute; + top: $top; + width: 750px; + left: $contentsWidth; + padding: 0px 100px; + border-bottom: 50px solid #f5f6fa; + line-height: 1.6; + font-size: 16px; + + h2 { + font-weight: 300; + font-size: 1.35em; + border-bottom: 1px solid #c6c6c6; + padding-bottom: 1rem; + margin-top: 2rem; + margin-bottom: 0.83em; + + &:first-of-type { + margin-top: 0; + } + } + + p, li { + font-size: 10pt; + } + + ul { + padding-left: 0; + li { + list-style-type: none; + margin-bottom: 10pt; + } + } + + .code { + background-color: #FFF; + font-family: "Source Code Pro", monospace; + font-size: 0.8rem; + padding: 1px 4px; + border: 1px solid #E3ECEC; + position: relative; + top: -1px; + display: inline-block; + } + } +} + +body { + background-color: #F9F9F9; + color: #19273C; +} \ No newline at end of file diff --git a/packages/letters-of-credit/src/utils/config.js b/packages/letters-of-credit/src/utils/config.js new file mode 100644 index 00000000..db1c09df --- /dev/null +++ b/packages/letters-of-credit/src/utils/config.js @@ -0,0 +1,49 @@ +class Config { + constructor() { + this.restServer = {}; + this.restServer.webSocketURL = "ws://localhost:3000"; + this.restServer.httpURL = "http://localhost:3000/api"; + this.restServer.explorer = "http://localhost:3000/explorer"; + + this.playground = {}; + this.playground.name = "Hyperledger Composer"; + this.playground.docURL = "https://hyperledger.github.io/composer/latest/"; + this.playground.deployedURL = "http://localhost:8080"; + + if (process.env.REACT_APP_REST_SERVER_CONFIG) { + try { + let restServerConfig = JSON.parse(process.env.REACT_APP_REST_SERVER_CONFIG); + if (restServerConfig.webSocketURL) { + this.restServer.webSocketURL = restServerConfig.webSocketURL; + } + if (restServerConfig.httpURL) { + this.restServer.httpURL = restServerConfig.httpURL; + } + if (restServerConfig.explorer) { + this.restServer.explorer = restServerConfig.explorer; + } + } catch (err) { + console.error('CONFIG ERROR', err); + } + } + + if (process.env.REACT_APP_PLAYGROUND_CONFIG) { + try { + let playgroundConfig = JSON.parse(process.env.REACT_APP_PLAYGROUND_CONFIG); + if (playgroundConfig.name) { + this.playground.name = playgroundConfig.name; + } + if (playgroundConfig.docURL) { + this.playground.docURL = playgroundConfig.docURL; + } + if (playgroundConfig.deployedURL) { + this.playground.deployedURL = playgroundConfig.deployedURL; + } + } catch (err) { + console.error('CONFIG ERROR', err); + } + } + } +} + +export default Config;