├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
└── index.html
└── src
├── App.vue
├── assets
└── graph.png
├── components
├── car
│ ├── CarCard.vue
│ ├── CarInfoWindow.vue
│ └── CarRegWindow.vue
├── help
│ └── DocWindow.vue
└── layout
│ ├── AppToolbar.vue
│ ├── LoadingAnim.vue
│ ├── NavDrawer.vue
│ ├── NotificationBar.vue
│ └── WalletHolder.vue
├── main.js
├── plugins
└── vuetify.js
├── router
└── index.js
├── store
├── index.js
└── modules
│ ├── info.js
│ └── layout.js
└── views
└── Car.vue
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 SIMBA Chain
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Car Demo 2
2 |
3 | > A Vue.js project for Simbachain Dapp Client (Live Demo: https://cardemo.simbachain.com)
4 |
5 | ## Project setup
6 |
7 | ##### 1. Create and configure an app on Simba Dashboard with the default `carDemo2` smart contract at `https://app.simbachain.com/`
8 |
9 | ##### 2. Generate an `apiKey` for the app on `App-View` page
10 |
11 | ##### 3. Open `src/store/modules/info.js` and replace `yourApiUrl` and `yourApiKey` with yours
12 |
13 | ```
14 | npm install
15 | ```
16 |
17 | ### Compiles and hot-reloads for development
18 | ```
19 | npm run serve
20 | ```
21 |
22 | ### Compiles and minifies for production
23 | ```
24 | npm run build
25 | ```
26 |
27 | ### Lints and fixes files
28 | ```
29 | npm run lint
30 | ```
31 |
32 | ### Customize configuration
33 | See [Configuration Reference](https://cli.vuejs.org/config/).
34 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cardemo2",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@simbachain/libsimba-js": "^0.5.3",
12 | "axios": "^0.19.2",
13 | "core-js": "^3.6.4",
14 | "vue": "^2.6.11",
15 | "vue-router": "^3.1.6",
16 | "vue-uuid": "^1.1.1",
17 | "vuetify": "^2.2.21",
18 | "vuex": "^3.1.3"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~4.3.0",
22 | "@vue/cli-plugin-eslint": "~4.3.0",
23 | "@vue/cli-service": "~4.3.0",
24 | "babel-eslint": "^10.1.0",
25 | "eslint": "^6.7.2",
26 | "eslint-plugin-vue": "^6.2.2",
27 | "vue-template-compiler": "^2.6.11"
28 | },
29 | "eslintConfig": {
30 | "root": true,
31 | "env": {
32 | "node": true
33 | },
34 | "extends": [
35 | "plugin:vue/essential",
36 | "eslint:recommended"
37 | ],
38 | "parserOptions": {
39 | "parser": "babel-eslint"
40 | },
41 | "rules": {}
42 | },
43 | "browserslist": [
44 | "> 1%",
45 | "last 2 versions",
46 | "not dead"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Car Demo 2
9 |
10 |
11 |
12 |
13 |
14 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SIMBAChain/CarDemo/fce2a7e4f583c37b2e56d75e88e4aa78278dee35/src/assets/graph.png
--------------------------------------------------------------------------------
/src/components/car/CarCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
14 |
21 | {{new Date(parseInt(currentCar.payload.inputs.time)).toDateString()}}
22 | {{currentCar.payload.inputs.make}} {{currentCar.payload.inputs.model}}
23 | VIN: {{currentCar.payload.inputs.vin}}
24 |
25 | Dealer: {{currentCar.payload.inputs.dealer}}
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/car/CarInfoWindow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | {{currentSelectedCar.payload.inputs.make}} {{currentSelectedCar.payload.inputs.model}}
11 |
12 | mdi-close
13 |
14 | VIN: {{currentSelectedCar.payload.inputs.vin}}
15 | Registered by: {{currentSelectedCar.payload.inputs.dealer}} on {{new Date(parseInt(currentSelectedCar.payload.inputs.time)).toDateString()}}
16 |
17 | Txn Hash: {{currentSelectedCar.transaction_hash}}
18 | IPFS Hash: {{currentSelectedCar.payload.inputs._bundleHash}}
19 |
20 |
21 |
22 |
23 | Report
24 | Inspector by {{report.payload.inputs.inspector}} on {{new Date(parseInt(report.payload.inputs.time)).toDateString()}}
25 | Condition: {{report.payload.inputs.condition}}
26 | Txn Hash: {{report.transaction_hash}}
27 |
28 |
29 |
30 |
31 |
32 | Sale
33 | Sold to {{sale.payload.inputs.buyer}} on {{new Date(parseInt(sale.payload.inputs.time)).toDateString()}}
34 | Price: {{sale.payload.inputs.price}}
35 | Txn Hash: {{report.transaction_hash}}
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 | Add an inspection report as an intent for sale
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Add a sell report and mark it as sold
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Checking Data...
78 | This transaction is complete, no further action can be taken
79 |
80 |
81 | {{walletStatus !== 'unlocked' ? 'Please wait wallet getting ready...' : ''}}
82 |
83 |
84 |
85 | Add Report
86 | Sale
87 | Post
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/components/car/CarRegWindow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Register Car
7 |
8 | mdi-close
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | *indicates required field
35 |
36 |
37 |
38 | {{walletStatus !== 'unlocked' ? 'Please wait wallet getting ready...' : ''}}
39 |
40 |
41 | Post
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/help/DocWindow.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | Help Doc
9 |
10 | mdi-close
11 |
12 |
13 |
14 | About
15 |
16 |
17 | Workflow
18 |
19 |
20 | Contract
21 |
22 |
23 | Wallet
24 |
25 |
26 |
27 |
28 |
29 | What is Car Demo 2?
30 |
31 | An updated version of the previous blockchain application(Car Demo) built with SIMBA API.
32 |
33 | What does this demo do?
34 |
35 | It simulates the process of car selling from car dealerships and provides transparent information(pre-owner cars, especially) to potential buyers.
36 |
37 | What are the advantages?
38 |
39 | It increases the transparency of pre-owner car markets and enhances data integrity.
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | How does Car Demo 2 work?
48 |
49 |
For the car dealers:
50 |
1. A car dealer owns a pre-owned car and posts the car information to blockchain as a registration in the system.
51 |
2. When the car dealer wants to sell the car, an third party inspector is invited to inspect the registered car and attach a report to that car on blockchain.
52 |
3. When the car is sold, the car dealer adds another report and marks the car as sold.
53 |
For the car buyers:
54 |
A buyer does not have to do anything except checking the information posted by car dealers
55 |
56 | You can find the example smart contract on SIMBA Dashboard.
57 | The contract structure can be found below.
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Where is my wallet?
70 |
71 | It is at the top corner with an icon mdi-wallet
72 |
73 | Do I need to create/restore a wallet?
74 |
75 | No. It is auto generated once you loaded this app. You are free to save the seed of the wallet.
76 |
77 | Why the balance shows N/A?
78 |
79 | This app is running on Quorum blockchain which does not require Ether for transactions.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/components/layout/AppToolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 | Car Demo 2
11 |
12 | mdi-help-circle-outline
13 |
14 |
15 |
16 |
17 |
18 |
46 |
47 |
50 |
--------------------------------------------------------------------------------
/src/components/layout/LoadingAnim.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/components/layout/NavDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
18 |
19 | {{ item.icon }}
20 |
21 |
22 |
23 | {{ item.text }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
57 |
--------------------------------------------------------------------------------
/src/components/layout/NotificationBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 | {{ snackbar ? snackbar.content : ''}}
11 |
12 |
13 | mdi-close
14 |
15 |
16 |
17 |
18 |
41 |
--------------------------------------------------------------------------------
/src/components/layout/WalletHolder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 | mdi-wallet
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{walletStatus === 'unlocked' ? 'mdi-lock-open-variant-outline': 'mdi-lock-outline'}}
23 |
24 | {{walletStatus}}
25 |
26 |
31 |
32 | Address: {{address}}
33 |
34 | Balance: N/A
35 |
36 |
37 | mdi-help-circle-outline
38 |
39 | No Ether Needed on Quorum
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Seed: {{seed.phrase}}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import vuetify from './plugins/vuetify'
4 | import router from './router'
5 | import store from './store'
6 | // import UUID from 'vue-uuid'
7 |
8 | Vue.config.productionTip = false
9 |
10 | // Vue.use(UUID)
11 |
12 | new Vue({
13 | vuetify,
14 | router,
15 | store,
16 | render: h => h(App),
17 | }).$mount('#app')
18 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import 'vuetify/dist/vuetify.min.css'
4 |
5 | Vue.use(Vuetify)
6 |
7 | const opts = {
8 | theme: {
9 | dark: true
10 | }
11 | }
12 |
13 | export default new Vuetify(opts)
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Car from '../views/Car.vue'
4 |
5 | Vue.use(VueRouter)
6 |
7 | const routes = [
8 | {
9 | path: '/',
10 | name: 'Car',
11 | component: Car
12 | }
13 | ]
14 |
15 | const router = new VueRouter({
16 | mode: 'history',
17 | base: process.env.BASE_URL,
18 | routes
19 | })
20 |
21 | export default router
22 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import createLogger from 'vuex/dist/logger'
4 |
5 | import layout from './modules/layout'
6 | import info from './modules/info'
7 |
8 | Vue.use(Vuex)
9 |
10 | const debug = process.env.NODE_ENV !== 'production'
11 |
12 | export default new Vuex.Store({
13 | modules: {
14 | layout,
15 | info
16 | },
17 | strict: debug,
18 | plugins: [
19 | ...(debug ? [createLogger()] : [])
20 | ]
21 | })
22 |
--------------------------------------------------------------------------------
/src/store/modules/info.js:
--------------------------------------------------------------------------------
1 | import * as libsimba from '@simbachain/libsimba-js'
2 |
3 | const url = 'yourApiUrl'
4 | const apiKey = 'YourApiKey'
5 |
6 | let wallet = new libsimba.LocalWallet()
7 | let simba = null
8 |
9 | // initial state
10 | const state = {
11 | address: null,
12 | walletStatus: null,
13 | simbaInitialized: false,
14 | cars: [],
15 | carsImg:[]
16 | }
17 |
18 | // getters
19 | const getters = {
20 | getSimba: () => simba,
21 | getWallet: async () => await wallet
22 | }
23 |
24 | // actions
25 | const actions = {
26 | async setSimba ({ commit, dispatch }) {
27 | commit('SET', { type: 'simbaInitialized', data: false })
28 | simba = await libsimba.getSimbaInstance(url, null, apiKey)
29 | commit('SET', { type: 'simbaInitialized', data: true })
30 | setInterval(() => dispatch('getCars'), 8000)
31 | },
32 | async getCars ({ state, commit, dispatch}) {
33 | let txns = await simba.getMethodTransactions('car', {})
34 | commit('SET', { type: 'cars', data: txns.data() })
35 | state.cars.filter(car => !state.carsImg.find(carImg => carImg.id === car.id)).forEach(noImg => dispatch('getCarImg', noImg.id))
36 | },
37 | async getCarImg({ state, commit }, payload) {
38 | let blob = await simba.getFileFromBundleForTransaction(payload, 0, false)
39 | if (state.carsImg.find(carImg => carImg.id === payload)) {
40 | return
41 | }
42 | commit('SET', { type: 'carsImg', data: [...state.carsImg, { id: payload, file: URL.createObjectURL(blob) }]})
43 | },
44 | async setWallet ({ commit }) {
45 | commit('SET', { type: 'walletStatus', data: 'init' })
46 |
47 | if (wallet.walletExists()) {
48 | commit('SET', { type: 'walletStatus', data: 'unlocking' })
49 | await wallet.unlockWallet('password')
50 | } else {
51 | commit('SET', { type: 'walletStatus', data: 'generating' })
52 | await wallet.generateWallet('password')
53 | }
54 | simba.setWallet(wallet)
55 | commit('SET', { type: 'walletStatus', data: 'unlocked' })
56 | commit('SET', { type: 'address', data: await wallet.getAddress() })
57 | }
58 | }
59 |
60 | // mutations
61 | const mutations = {
62 | SET (state, payload) {
63 | state[payload.type] = payload.data
64 | }
65 | }
66 |
67 | export default {
68 | namespaced: true,
69 | state,
70 | getters,
71 | actions,
72 | mutations
73 | }
74 |
--------------------------------------------------------------------------------
/src/store/modules/layout.js:
--------------------------------------------------------------------------------
1 | // initial state
2 | const state = {
3 | snackbar: null,
4 | drawer: false,
5 |
6 | carRegWindow: false,
7 | carInfoWindow: false,
8 | currentSelectedCarId: null,
9 |
10 | helpDocWindow: false
11 | }
12 |
13 | // getters
14 | const getters = {}
15 |
16 | // actions
17 | const actions = {
18 |
19 | }
20 |
21 | // mutations
22 | const mutations = {
23 | SET (state, payload) {
24 | state[payload.type] = payload.data
25 | }
26 | }
27 |
28 | export default {
29 | namespaced: true,
30 | state,
31 | getters,
32 | actions,
33 | mutations
34 | }
35 |
--------------------------------------------------------------------------------
/src/views/Car.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
33 | mdi-plus
34 |
35 |
36 |
37 |
38 |
80 |
--------------------------------------------------------------------------------