├── .env ├── .eslintrc.js ├── .firebaserc ├── .gitignore ├── README.md ├── babel.config.js ├── firebase.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── button-login.svg │ ├── fonts │ │ ├── element-icons.ttf │ │ └── element-icons.woff │ ├── gif1.gif │ ├── logo-large.svg │ └── logo.svg ├── components │ ├── content │ │ ├── HelpDeveloper.vue │ │ └── HelpDonator.vue │ ├── core │ │ ├── Icon.vue │ │ ├── IssueBox.vue │ │ ├── JobLink.vue │ │ ├── RepoBox.vue │ │ └── SectionBar.vue │ ├── layout │ │ ├── Footer.vue │ │ ├── Header.vue │ │ └── Sidebar.vue │ └── pages │ │ ├── Contact.vue │ │ ├── Dashboard.vue │ │ ├── ExternalLogin.vue │ │ ├── Issues.vue │ │ ├── Login.vue │ │ └── Repos.vue ├── config.js ├── filters │ └── truncate.js ├── firebase.js ├── main.js ├── mixins │ ├── entities.js │ ├── github.js │ ├── sidebar.js │ └── user.js ├── router.js ├── services │ ├── cloud-function-service.js │ ├── etherscan-service.js │ ├── github-public-service.js │ └── web3-service.js ├── store.js ├── styles │ ├── _theme.scss │ ├── element-variables.scss │ ├── main.scss │ └── sidebar │ │ ├── var.scss │ │ ├── vue-sidebar-menu.scss │ │ └── white-theme.scss └── utils.js └── vue.config.js /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_firebaseConfig_apiKey=AIzaSyBwtnfLAqR65OuMhqlNyAdkHaOwHJZC6S4 2 | VUE_APP_firebaseConfig_authDomain=gitman-app.firebaseapp.com 3 | VUE_APP_firebaseConfig_databaseURL=https://gitman-app.firebaseio.com 4 | VUE_APP_firebaseConfig_projectId=gitman-app 5 | VUE_APP_firebaseConfig_storageBucket=gitman-app.appspot.com 6 | VUE_APP_firebaseConfig_messagingSenderId=376498363871 7 | VUE_APP_ethereumConfig_mainnetFactoryContract="0x3096cB13d2Cd4158d98ca97481C06a54fa13BBE9" 8 | VUE_APP_ethereumConfig_rinkebyFactoryContract="0x30F6dEf51a843D6Eb54f27Ce9c69aD0bc408ac50" 9 | VUE_APP_firebaseConfig_functionsUrl=https://us-central1-gitman-app.cloudfunctions.net 10 | VUE_APP_ethereumConfig_factoryContractAbi="[{"constant":false,"inputs":[{"name":"user","type":"string"},{"name":"repository","type":"string"},{"name":"issue","type":"string"}],"name":"createIssue","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"share","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint8"}],"name":"setShare","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"contractAddress","type":"address"},{"indexed":false,"name":"issue","type":"string"}],"name":"IssueCreated","type":"event"}]" -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "gitman-app": "gitman-app", 4 | "default": "gitman-app" 5 | } 6 | } -------------------------------------------------------------------------------- /.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 | ### Node template 23 | # Logs 24 | logs 25 | *.log 26 | lerna-debug.log* 27 | 28 | # Diagnostic reports (https://nodejs.org/api/report.html) 29 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 30 | 31 | # Runtime data 32 | pids 33 | *.pid 34 | *.seed 35 | *.pid.lock 36 | 37 | # Directory for instrumented libs generated by jscoverage/JSCover 38 | lib-cov 39 | 40 | # Coverage directory used by tools like istanbul 41 | coverage 42 | *.lcov 43 | 44 | # nyc test coverage 45 | .nyc_output 46 | 47 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 48 | .grunt 49 | 50 | # Bower dependency directory (https://bower.io/) 51 | bower_components 52 | 53 | # node-waf configuration 54 | .lock-wscript 55 | 56 | # Compiled binary addons (https://nodejs.org/api/addons.html) 57 | build/Release 58 | 59 | # Dependency directories 60 | node_modules/ 61 | jspm_packages/ 62 | 63 | # TypeScript v1 declaration files 64 | typings/ 65 | 66 | # TypeScript cache 67 | *.tsbuildinfo 68 | 69 | # Optional npm cache directory 70 | .npm 71 | 72 | # Optional eslint cache 73 | .eslintcache 74 | 75 | # Optional REPL history 76 | .node_repl_history 77 | 78 | # Output of 'npm pack' 79 | *.tgz 80 | 81 | # Yarn Integrity file 82 | .yarn-integrity 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | 87 | # next.js build output 88 | .next 89 | 90 | # nuxt.js build output 91 | .nuxt 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | ### JetBrains template 106 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 107 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 108 | 109 | # User-specific stuff 110 | .idea/**/workspace.xml 111 | .idea/**/tasks.xml 112 | .idea/**/usage.statistics.xml 113 | .idea/**/dictionaries 114 | .idea/**/shelf 115 | 116 | # Generated files 117 | .idea/**/contentModel.xml 118 | 119 | # Sensitive or high-churn files 120 | .idea/**/dataSources/ 121 | .idea/**/dataSources.ids 122 | .idea/**/dataSources.local.xml 123 | .idea/**/sqlDataSources.xml 124 | .idea/**/dynamic.xml 125 | .idea/**/uiDesigner.xml 126 | .idea/**/dbnavigator.xml 127 | 128 | # Gradle 129 | .idea/**/gradle.xml 130 | .idea/**/libraries 131 | 132 | # Gradle and Maven with auto-import 133 | # When using Gradle or Maven with auto-import, you should exclude module files, 134 | # since they will be recreated, and may cause churn. Uncomment if using 135 | # auto-import. 136 | # .idea/modules.xml 137 | # .idea/*.iml 138 | # .idea/modules 139 | # *.iml 140 | # *.ipr 141 | 142 | # CMake 143 | cmake-build-*/ 144 | 145 | # Mongo Explorer plugin 146 | .idea/**/mongoSettings.xml 147 | 148 | # File-based project format 149 | *.iws 150 | 151 | # IntelliJ 152 | out/ 153 | 154 | # mpeltonen/sbt-idea plugin 155 | .idea_modules/ 156 | 157 | # JIRA plugin 158 | atlassian-ide-plugin.xml 159 | 160 | # Cursive Clojure plugin 161 | .idea/replstate.xml 162 | 163 | # Crashlytics plugin (for Android Studio and IntelliJ) 164 | com_crashlytics_export_strings.xml 165 | crashlytics.properties 166 | crashlytics-build.properties 167 | fabric.properties 168 | 169 | # Editor-based Rest Client 170 | .idea/httpRequests 171 | 172 | # Android studio 3.1+ serialized cache file 173 | .idea/caches/build_file_checksums.ser 174 | 175 | ### VisualStudioCode template 176 | .vscode/* 177 | !.vscode/settings.json 178 | !.vscode/tasks.json 179 | !.vscode/launch.json 180 | !.vscode/extensions.json 181 | 182 | .browserslistrc 183 | .firebase/ 184 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Gitman 2 | 3 | > Complete Github issues and earn Ethereum. 4 | 5 | Gitman is an application that makes the process of adding a reward to Github issues and sending the reward very easy. 6 | 7 | ##### The process is as simple as: 8 | - Add a bounty to a Github issue, the issue can be a new issue or you can sponsor an existing issue 9 | - A developer completes the issue and submits a pull request 10 | - Approve the pull request and the reward is sent to the developer who submitted the pull request 11 | 12 | Below is a link to the application: 13 | 14 | [Gitman app](https://live.gitman.app) 15 | 16 | a link to a video of the app in action: 17 | 18 | [Gitman video](https://youtu.be/pw3AFkWl2qc) 19 | 20 | and a link to our website: 21 | 22 | [Gitman website](https://www.gitman.app/) 23 | 24 | ##### Running the UI code 25 | 26 | ``` npm run serve ``` 27 | 28 | ##### Gitman API 29 | 30 | We have also documented the API so others can consume it and create their own UI to facilitate the workflow. 31 | 32 | [API Doco](https://documenter.getpostman.com/view/41839/S1TYVGY7?version=latest) 33 | 34 | ##### Fees 35 | 36 | At the moment Gitman takes a 3% fee when a bounty is added to an issue this is to cover the running costs 37 | 38 | ##### Chat to us
 39 | 40 | We would love to chat to anyone about this project via the telegram link below: 41 | 42 | [telegram](https://t.me/joinchat/Gr-5fxF7FTHdNfzKQKJGgQ) 43 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "firestore": { 6 | "rules": "firestore.rules", 7 | "indexes": "firestore.indexes.json" 8 | }, 9 | "hosting": { 10 | "headers": [ 11 | { 12 | "source": "**/*.@(css|js)", 13 | "headers": [ 14 | { 15 | "key": "Cache-Control", 16 | "value": "max-age=0, no-cache" 17 | } 18 | ] 19 | } 20 | ], 21 | "public": "dist", 22 | "ignore": [ 23 | "firebase.json", 24 | "**/.*", 25 | "**/node_modules/**" 26 | ] 27 | }, 28 | "storage": { 29 | "rules": "storage.rules" 30 | }, 31 | "functions": { 32 | "predeploy": "npm run build", 33 | "source": "." 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitman-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vue-cli-service serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "bootstrap-vue": "^2.0.0-rc.22", 13 | "element-ui": "^2.9.1", 14 | "firebase": "^5.11.1", 15 | "moment": "^2.24.0", 16 | "octicons": "^8.5.0", 17 | "semantic-ui-css": "^2.4.1", 18 | "vue": "^2.6.10", 19 | "vue-resource": "^1.3.4", 20 | "vue-router": "^3.0.6", 21 | "vue-slide-up-down": "^1.7.2", 22 | "vue-snack": "^0.1.4", 23 | "vuefire": "^1.4.4", 24 | "vuelidate": "^0.7.4", 25 | "vuex": "^3.1.1", 26 | "web3": "^1.2.6" 27 | }, 28 | "devDependencies": { 29 | "@vue/cli-plugin-babel": "^3.8.0", 30 | "@vue/cli-plugin-eslint": "^3.8.0", 31 | "@vue/cli-service": "^3.8.0", 32 | "@vue/eslint-config-standard": "^4.0.0", 33 | "css-loader": "^1.0.1", 34 | "element-theme": "^2.0.1", 35 | "element-theme-chalk": "^2.9.1", 36 | "eslint": "^5.16.0", 37 | "eslint-plugin-vue": "^5.2.2", 38 | "less-loader": "^4.1.0", 39 | "node-sass": "^4.12.0", 40 | "pug": "^2.0.0-rc.4", 41 | "pug-plain-loader": "^1.0.0", 42 | "reset-css": "^4.0.1", 43 | "sass-loader": "^7.0.1", 44 | "style-loader": "^0.23.1", 45 | "vue-loader": "^15.7.0", 46 | "vue-template-compiler": "^2.6.10" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigvl/gitmanUI/cd48724fc0427d49ae3fed0d568b5066956cb8bf/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | gitman 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 70 | 71 | 74 | 75 | 80 | -------------------------------------------------------------------------------- /src/assets/button-login.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | Continue with Github 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigvl/gitmanUI/cd48724fc0427d49ae3fed0d568b5066956cb8bf/src/assets/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/assets/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigvl/gitmanUI/cd48724fc0427d49ae3fed0d568b5066956cb8bf/src/assets/fonts/element-icons.woff -------------------------------------------------------------------------------- /src/assets/gif1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigvl/gitmanUI/cd48724fc0427d49ae3fed0d568b5066956cb8bf/src/assets/gif1.gif -------------------------------------------------------------------------------- /src/assets/logo-large.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 77 | 79 | 81 | 83 | 85 | 87 | 88 | 89 | 90 | 92 | 94 | 96 | 98 | 100 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 119 | 121 | 123 | 124 | 125 | 126 | 127 | 128 | 130 | 131 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/components/content/HelpDeveloper.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 80 | -------------------------------------------------------------------------------- /src/components/content/HelpDonator.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 66 | -------------------------------------------------------------------------------- /src/components/core/Icon.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 87 | 88 | 101 | -------------------------------------------------------------------------------- /src/components/core/IssueBox.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 338 | -------------------------------------------------------------------------------- /src/components/core/JobLink.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 56 | 57 | 63 | -------------------------------------------------------------------------------- /src/components/core/RepoBox.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 73 | -------------------------------------------------------------------------------- /src/components/core/SectionBar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /src/components/layout/Footer.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/components/layout/Header.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 64 | -------------------------------------------------------------------------------- /src/components/layout/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 128 | 129 | 132 | 133 | 150 | -------------------------------------------------------------------------------- /src/components/pages/Contact.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 42 | -------------------------------------------------------------------------------- /src/components/pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 157 | -------------------------------------------------------------------------------- /src/components/pages/ExternalLogin.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 75 | -------------------------------------------------------------------------------- /src/components/pages/Issues.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 124 | -------------------------------------------------------------------------------- /src/components/pages/Login.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 48 | -------------------------------------------------------------------------------- /src/components/pages/Repos.vue: -------------------------------------------------------------------------------- 1 | 125 | 126 | 456 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | function buildConfig (name) { 2 | return Object.keys(process.env) 3 | .filter(key => key.startsWith(`VUE_APP_${name}_`)) 4 | .reduce((a, c) => ({ ...a, [c.replace(`VUE_APP_${name}_`, '')]: process.env[c] }), {}) 5 | } 6 | 7 | export const firebaseConfig = buildConfig('firebaseConfig') 8 | export const ethereumConfig = buildConfig('ethereumConfig') 9 | -------------------------------------------------------------------------------- /src/filters/truncate.js: -------------------------------------------------------------------------------- 1 | module.exports = function (text, stop, clamp) { 2 | return text.slice(0, stop) + (stop < text.length ? clamp || '...' : '') 3 | } 4 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import { firebaseConfig } from './config' 2 | import firebase from 'firebase' 3 | import 'firebase/functions' 4 | 5 | const firebaseApp = firebase.initializeApp(firebaseConfig) 6 | if (firebaseConfig.emulator) { 7 | console.warn(`using emulator on ${firebaseConfig.emulator}`) 8 | firebaseApp.functions().useFunctionsEmulator(firebaseConfig.emulator) 9 | } 10 | const database = firebase.database() 11 | const auth = firebase.auth() 12 | const functions = firebase.functions() 13 | const githubProvider = new firebase.auth.GithubAuthProvider() 14 | 15 | // githubProvider.addScope('admin:repo_hook') 16 | githubProvider.addScope('read:org') 17 | githubProvider.addScope('public_repo') 18 | githubProvider.addScope('read:user') 19 | 20 | export { 21 | firebaseApp, 22 | database, 23 | auth, 24 | functions, 25 | githubProvider 26 | } 27 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import Vuefire from 'vuefire' 6 | import VueResource from 'vue-resource' 7 | import VueSnackbar from 'vue-snack' 8 | import ElementUI from 'element-ui' 9 | import Vuelidate from 'vuelidate' 10 | //import { VBTooltip } from 'bootstrap-vue' Vue.use(VBTooltip ) used ??? 11 | import CloudFunction from '@/services/cloud-function-service' 12 | import EtherscanService from '@/services/etherscan-service' 13 | import Web3Service from '@/services/web3-service' 14 | import GithubPublicService from '@/services/github-public-service' 15 | import { functions } from '@/firebase' // config and prepare firebase app 16 | import { ethereumConfig } from './config' 17 | import './utils' 18 | import SlideUpDown from 'vue-slide-up-down' 19 | 20 | Vue.component('slide-up-down', SlideUpDown) 21 | Vue.use(VueResource) 22 | Vue.use(Vuefire) 23 | Vue.use(ElementUI) 24 | 25 | if (!ethereumConfig.mainnetFactoryContract) throw `config error missing 'mainnetFactoryContract' key` // throws an exception with a numeric value 26 | if (!ethereumConfig.rinkebyFactoryContract) throw `config error missing 'rinkebyFactoryContract' key` // throws an exception with a numeric value 27 | 28 | Vue.use(VueSnackbar, { close: false, time: 20000 }) 29 | Vue.use(Vuelidate) 30 | Vue.use(CloudFunction, { firebaseFunctions: functions }) 31 | Vue.use(EtherscanService, { uri: 'https://api.coinmarketcap.com' }) 32 | Vue.use(Web3Service, ethereumConfig) 33 | Vue.use(GithubPublicService, { apiUrl: `https://api.github.com` }) 34 | 35 | Vue.config.productionTip = false 36 | 37 | // js config 38 | Vue.prototype.$admins = ['thierry.rais@gmail.com', 'undead_craig@hotmail.com', 'craigvl@tpg.com.au'] 39 | Vue.prototype.$rewardAmounts = [0.001, 0.01, 0.06, 0.1, 0.2, 0.4, 0.8, 1] 40 | 41 | new Vue({ 42 | router, 43 | store, 44 | render: h => h(App) 45 | }).$mount('#app') 46 | -------------------------------------------------------------------------------- /src/mixins/entities.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | jobs () { 4 | return this.$root.$children[0].firebaseJobs.reduce((a, c) => a.concat(Object.keys(c).filter(_ => _ !== '.key').map(_ => c[_])), []) 5 | }, 6 | issues () { 7 | return this.$root.$children[0].firebaseIssues 8 | }, 9 | userJobs () { 10 | return this.$root.$children[0].firebaseUserJobs 11 | }, 12 | hookRepos () { 13 | return this.$root.$children[0].firebaseRepos 14 | }, 15 | // repos () { 16 | // return this.$root.$children[0].userGitRepos 17 | // }, 18 | userCreatedIssues () { 19 | return this.$root.$children[0].firebaseIssues.filter(_ => _.ownerKey === this.$root.$children[0].firebaseUser['.key']) 20 | }, 21 | userContributingIssues () { 22 | return this.$root.$children[0].firebaseIssues.filter(i => this.$root.$children[0].firebaseUserJobs.some(j => i.id === j.issueId)) 23 | }, 24 | issuesWithJobs () { 25 | return this.$root.$children[0].firebaseIssues.map(issue => ({ 26 | issue: issue, 27 | jobs: this.$root.$children[0].firebaseJobs 28 | .reduce((a, c) => a.concat(Object.keys(c).filter(_ => _ !== '.key').map(_ => c[_])), []) 29 | .filter(job => job.issueId === issue.id) 30 | })) 31 | } 32 | // registeredOwnedRepos () { 33 | // const hookUserRepos = this.$root.$children[0].firebaseRepos.map(_ => _.name) 34 | 35 | // return this.$root.$children[0].userGitRepos 36 | // .filter(_ => _.permissions.admin && !_.fork && hookUserRepos.indexOf(_.full_name) >= 0) 37 | // .map(_ => ({ ..._, name: _.full_name, hasHook: true })) 38 | // .unique(_ => _.name) 39 | // } 40 | }, 41 | methods: { 42 | equalOrNull (value, test) { 43 | return test === undefined || (value && value.toLowerCase()) === test.toLowerCase() 44 | }, 45 | isIssueActive (network, completed) { 46 | return _ => (completed ? _.status && _.status.toLowerCase() === 'closed' : _.status && _.status.toLowerCase() !== 'closed') && this.equalOrNull(_.contractNetwork, network) 47 | }, 48 | isJobActive (issue) { 49 | // add more checks 50 | return _ => issue.status.toLowerCase() !== 'closed' 51 | }, 52 | getJobStatus (_) { 53 | if (_.branchUrl) return 'pending' // working 54 | if (_.pullRequestNumber) return 'ready' // ready for review' 55 | if (_.forking) return 'forking' 56 | }, 57 | getJobsForIssue (issueId) { 58 | return jobs => jobs 59 | .reduce((a, c) => a.concat(Object.keys(c).filter(_ => _ !== '.key').map(_ => c[_])), []) 60 | .filter(_ => _.issueId === issueId) 61 | }, 62 | getJobIssues (jobs) { 63 | return issues => issues.filter(_ => jobs.find(j => j.issueKey === _['.key'])) 64 | }, 65 | queryRepos (q) { 66 | return repos => repos 67 | .filter(_ => _.name.split('/')[0] !== this.user.gitUserName && !_.private) 68 | .searchBy(['name'], this.searchSponsorRepo) 69 | }, 70 | queryIssues (q) { 71 | // const { 72 | // search, 73 | // ownerUsername, 74 | // isActive, 75 | // network 76 | // } = q 77 | 78 | return issues => issues 79 | .filter(_ => this.isIssueActive(q.network, !q.isActive)(_) && this.equalOrNull(_.ownerUsername, q.ownerUsername)) 80 | .searchBy(['title', 'tags', 'repositoryName'], q.search) 81 | .sort((x, y) => y.createdAt - x.createdAt) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/mixins/github.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | pullRequestUrl (repositoryName, pullRequestNumber) { 4 | return `https://github.com/${repositoryName}/pull/${pullRequestNumber}` 5 | }, 6 | forkedBranchUrl (forkName, branchUrl) { 7 | return `https://github.com/${forkName}/tree/${branchUrl.split('/')[2]}` 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/mixins/sidebar.js: -------------------------------------------------------------------------------- 1 | export const itemMixin = { 2 | data () { 3 | return { 4 | active: false, 5 | childActive: false, 6 | itemShow: false 7 | } 8 | }, 9 | created () { 10 | this.active = this.item && this.item.href ? this.isLinkActive(this.item) : false 11 | this.childActive = this.item && this.item.child ? this.isChildActive(this.item.child) : false 12 | if (this.item && this.item.child) { 13 | if (this.showChild) { 14 | this.itemShow = true 15 | } else { 16 | this.itemShow = this.isLinkActive(this.item) || this.isChildActive(this.item.child) 17 | if (this.showOneChild && !this.showChild && (this.active || this.childActive) && this.firstItem) { 18 | this.emitActiveShow(this._uid) 19 | } 20 | } 21 | } 22 | 23 | if (!this.$router) { 24 | window.addEventListener('hashchange', () => { 25 | this.active = this.item && this.item.href ? this.isLinkActive(this.item) : false 26 | this.childActive = this.item && this.item.child ? this.isChildActive(this.item.child) : false 27 | }) 28 | } 29 | }, 30 | methods: { 31 | toggleDropdown () { 32 | this.itemShow = !this.itemShow 33 | }, 34 | isLinkActive (item) { 35 | if (this.$route) { 36 | return item.href === this.$route.path + this.$route.hash 37 | } else { 38 | return item.href === window.location.pathname + window.location.hash 39 | } 40 | }, 41 | isChildActive (child) { 42 | for (let item of child) { 43 | if (this.isLinkActive(item)) { 44 | return true 45 | } 46 | if (item.child) { 47 | if (this.isChildActive(item.child)) { 48 | return true 49 | } 50 | } 51 | } 52 | return false 53 | }, 54 | clickEvent (event, mobileItem) { 55 | this.emitItemClick(event, this.item) 56 | 57 | if (this.item.disabled || (mobileItem && !this.item.href)) { 58 | event.preventDefault() 59 | return 60 | } 61 | 62 | if (this.isCollapsed && this.firstItem) { 63 | let clearCloseTimeout = this.item.child 64 | this.$parent.$emit('touchClickItem', clearCloseTimeout) 65 | } 66 | 67 | if (!mobileItem && this.item.child) { 68 | if (this.isCollapsed && this.firstItem) { 69 | event.preventDefault() 70 | return 71 | } 72 | if (this.isRouterLink) { 73 | if (this.firstItem && this.showOneChild && !this.showChild) { 74 | if (this.active) { 75 | if (this.activeShow.uid === this._uid) { 76 | this.itemShow = false 77 | this.emitActiveShow(null) 78 | } else { 79 | this.itemShow = true 80 | this.emitActiveShow(this._uid) 81 | } 82 | } else { 83 | this.itemShow = true 84 | this.emitActiveShow(this._uid) 85 | } 86 | } else { 87 | this.active ? this.toggleDropdown() : this.itemShow = true 88 | } 89 | } else if (!this.item.href) { 90 | event.preventDefault() 91 | if (this.firstItem && this.showOneChild && !this.showChild) { 92 | if (this.activeShow.uid === this._uid) { 93 | this.itemShow = false 94 | this.emitActiveShow(null) 95 | } else { 96 | this.itemShow = true 97 | this.emitActiveShow(this._uid) 98 | } 99 | } else { 100 | this.toggleDropdown() 101 | } 102 | } 103 | } else if (!mobileItem && !this.isCollapsed && this.firstItem && !this.item.child) { 104 | this.emitActiveShow(null) 105 | } 106 | } 107 | }, 108 | computed: { 109 | isRouterLink () { 110 | return this.$router && this.item && this.item.href !== undefined 111 | }, 112 | show () { 113 | if (!this.item || !this.item.child) return false 114 | if (this.firstItem && this.showOneChild && !this.showChild) { 115 | if (!this.activeShow.uid) { 116 | return false 117 | } else { 118 | return this._uid === this.activeShow.uid 119 | } 120 | } else { 121 | return this.itemShow 122 | } 123 | } 124 | }, 125 | watch: { 126 | $route () { 127 | this.active = this.item && this.item.href ? this.isLinkActive(this.item) : false 128 | this.childActive = this.item && this.item.child ? this.isChildActive(this.item.child) : false 129 | } 130 | }, 131 | inject: ['showChild', 'showOneChild', 'emitActiveShow', 'activeShow', 'emitItemClick', 'rtl'] 132 | } 133 | 134 | export const animationMixin = { 135 | methods: { 136 | expandEnter (el) { 137 | el.style.height = el.scrollHeight + 'px' 138 | }, 139 | expandAfterEnter (el) { 140 | el.style.height = 'auto' 141 | }, 142 | expandBeforeLeave (el) { 143 | if (this.isCollapsed) { 144 | el.style.display = 'none' 145 | return 146 | } 147 | el.style.height = el.scrollHeight + 'px' 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/mixins/user.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | user () { 4 | return { 5 | ...this.$root.$children[0].firebaseUser, 6 | key: this.$root.$children[0].firebaseUser['.key'], 7 | isAdmin: this.$admins.includes(this.$root.$children[0].firebaseUser.email) 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Login from '@/components/pages/Login' 4 | import Repos from '@/components/pages/Repos' 5 | import Issues from '@/components/pages/Issues' 6 | import Contact from '@/components/pages/Contact' 7 | import Dashboard from '@/components/pages/Dashboard' 8 | import ExternalLogin from '@/components/pages/ExternalLogin' 9 | 10 | import { auth, database } from '@/firebase' 11 | 12 | Vue.use(Router) 13 | 14 | const navGuard = (to, from, next) => { 15 | auth.onAuthStateChanged(user => { 16 | if (!user) { 17 | next({ path: '/login', query: { redirect: to.fullPath } }) 18 | } else { 19 | next() 20 | } 21 | }) 22 | } 23 | 24 | // const hashScroll = (to, from, next) => { 25 | // if (!to.hash && to.name) { 26 | // next('login') 27 | // } else { 28 | // next() 29 | // } 30 | // } 31 | 32 | export default new Router({ 33 | // mode: 'history', 34 | // scrollBehavior (to, from, savedPosition) { 35 | // if (to.hash && to.name === 'register') { 36 | // return { 37 | // selector: to.hash 38 | // } 39 | // } 40 | // }, 41 | routes: [ 42 | { 43 | path: '/login', 44 | name: 'login', 45 | component: Login, 46 | beforeEnter (to, from, next) { 47 | // console.dir(from) 48 | // console.dir(to) 49 | 50 | auth.onAuthStateChanged(user => { 51 | if (!user) { 52 | next() 53 | } 54 | auth.getRedirectResult() 55 | .then(result => { 56 | if (result.credential) { 57 | const token = result.credential.accessToken 58 | // this.$gitService.getUserOwnedRepos(token).then(_ => { 59 | // }) 60 | database.ref(`user/${result.user.uid}`).update({ 61 | name: result.user.displayName, 62 | email: result.user.email, 63 | photoUrl: result.user.photoURL, 64 | gitAccessToken: token, 65 | id: result.additionalUserInfo.profile.id, 66 | gitUserName: result.additionalUserInfo.username 67 | }) 68 | } 69 | next(to.query.redirect || 'issues') 70 | }).catch(error => { 71 | next(new Error(error.message ? error.message : error)) 72 | }) 73 | }) 74 | } 75 | }, 76 | { 77 | path: '/repos', 78 | name: 'repos', 79 | component: Repos, 80 | beforeEnter (to, from, next) { 81 | navGuard(to, from, next) 82 | } 83 | }, 84 | { 85 | path: '/repos/:enableId', 86 | name: 'register', 87 | props: true, 88 | component: Repos, 89 | beforeEnter (to, from, next) { 90 | navGuard(to, from, next) 91 | } 92 | }, 93 | { 94 | path: '/issues', 95 | name: 'issues', 96 | component: Issues, 97 | beforeEnter (to, from, next) { 98 | navGuard(to, from, next) 99 | } 100 | }, 101 | // { 102 | // path: '/issue/:id', 103 | // name: 'issue', 104 | // component: Issue, 105 | // beforeEnter (to, from, next) { 106 | // navGuard(to, from, next) 107 | // } 108 | // }, 109 | { 110 | path: '/dashboard', 111 | name: 'dashboard', 112 | component: Dashboard, 113 | beforeEnter (to, from, next) { 114 | navGuard(to, from, next) 115 | } 116 | }, 117 | { 118 | path: '/contact', 119 | name: 'contact', 120 | component: Contact, 121 | beforeEnter (to, from, next) { 122 | navGuard(to, from, next) 123 | } 124 | }, 125 | { 126 | path: '/ext/login', 127 | name: 'ext-login', 128 | component: ExternalLogin, 129 | beforeEnter (to, from, next) { 130 | navGuard(to, from, next) 131 | } 132 | }, 133 | { 134 | path: '*', 135 | redirect: '/issues' 136 | } 137 | ] 138 | }) 139 | -------------------------------------------------------------------------------- /src/services/cloud-function-service.js: -------------------------------------------------------------------------------- 1 | class CloudFunctionService { 2 | constructor (config) { 3 | this.firebaseFunctions = config.firebaseFunctions 4 | this.ethUSDRate = null 5 | } 6 | 7 | async authExternalUser (appKey, token) { 8 | const auth = this.firebaseFunctions.httpsCallable('authExternalUser') 9 | const res = await auth({ 10 | appKey, 11 | token 12 | }) 13 | return res.data 14 | } 15 | 16 | async submitWork (head, repoName, branch, issueId) { 17 | const createPullRequest = this.firebaseFunctions.httpsCallable('submitWork') 18 | const res = await createPullRequest({ 19 | head: head, 20 | repoName: repoName, 21 | branch: branch, 22 | issueId 23 | }) 24 | return res.data 25 | } 26 | 27 | async getBranches (repositoryName) { 28 | const getBranches = this.firebaseFunctions.httpsCallable('getBranches') 29 | const res = await getBranches({ repositoryName }) 30 | return res.data 31 | } 32 | 33 | async getOwnedRepos () { 34 | const getRepos = this.firebaseFunctions.httpsCallable('getOwnedRepos') 35 | const res = await getRepos() 36 | return res.data 37 | } 38 | 39 | async registerRepo (repositoryName) { 40 | const create = this.firebaseFunctions.httpsCallable(`registerRepo`) 41 | const res = await create({ repositoryName }) 42 | return res.data 43 | } 44 | 45 | async deregisterRepo (repositoryName) { 46 | const remove = this.firebaseFunctions.httpsCallable(`deregisterRepo`) 47 | const res = await remove({ repositoryName }) 48 | return res.data 49 | } 50 | 51 | async startWork (userKey, issueKey) { 52 | const res = await this.firebaseFunctions.httpsCallable(`startWork`)({ 53 | userKey, 54 | issueKey 55 | }) 56 | return res.data 57 | } 58 | 59 | async saveWallet (userKey, address, network) { 60 | const res = await this.firebaseFunctions.httpsCallable(`saveWallet`)({ 61 | network, 62 | userKey, 63 | address 64 | }) 65 | return res.data 66 | } 67 | 68 | async addIssue (userKey, repositoryName, branch, title, description, network) { 69 | const addIssue = this.firebaseFunctions.httpsCallable(`addIssue`) 70 | const res = await addIssue({ 71 | title, 72 | description, 73 | branch, 74 | repositoryName, 75 | userKey, 76 | network 77 | }) 78 | return res.data 79 | } 80 | 81 | async sponsorIssue (userKey, repositoryName, branch, issueNumber, network) { 82 | const sponsorIssue = this.firebaseFunctions.httpsCallable(`sponsorIssue`) 83 | const res = await sponsorIssue({ 84 | issueNumber, 85 | branch, 86 | repositoryName, 87 | userKey, 88 | network 89 | }) 90 | return res.data 91 | } 92 | 93 | async cancelJob (issueKey) { 94 | const cancelIssue = this.firebaseFunctions.httpsCallable(`cancelJob`) 95 | await cancelIssue({ 96 | issueKey 97 | }) 98 | } 99 | 100 | async cancelIssue (issueKey) { 101 | const cancelIssue = this.firebaseFunctions.httpsCallable(`cancelIssue`) 102 | await cancelIssue({ 103 | issueKey 104 | }) 105 | } 106 | 107 | async fetchEthUSD () { 108 | if (!this.ethUSDRate) { 109 | const res = await this.firebaseFunctions.httpsCallable(`getUSD`)() 110 | var result = JSON.parse(res.data) 111 | this.ethUSDRate = parseInt(result.data.ETH.quote.USD.price) 112 | } 113 | return this.ethUSDRate 114 | } 115 | 116 | /// verifiy the contract and attach value and other to FB issue 117 | async startFundIssue (issueKey, transaction, contractNetwork) { 118 | const res = await this.firebaseFunctions.httpsCallable(`startFundIssue`)({ 119 | issueKey, 120 | transaction, 121 | contractNetwork 122 | }) 123 | return res.data 124 | } 125 | 126 | /// verifiy the contract and attach value and other to FB issue 127 | async completeFundIssue (issueKey, contractAddress, contractNetwork) { 128 | const res = await this.firebaseFunctions.httpsCallable(`completeFundIssue`)({ 129 | issueKey, 130 | contractAddress, 131 | contractNetwork 132 | }) 133 | return res.data 134 | } 135 | 136 | // deleteHook (gitAccessToken, userKey, ownerName, repoName, repoId) { 137 | // const deleteHook = this.firebaseFunctions.httpsCallable('app/deleteHook') 138 | 139 | // return new Promise((resolve, reject) => { 140 | // deleteHook({ 141 | // gitAccessToken: gitAccessToken, 142 | // repoOwner: ownerName, 143 | // repoName: repoName, 144 | // repoId: repoId, 145 | // userKey 146 | // }).then(response => { 147 | // if (response.status !== 200 || parseInt(response.body.status) === 0) { 148 | // const message = `error creating hook, err: ${response}` 149 | // reject(message) 150 | // } else { 151 | // resolve(response.body.value) 152 | // } 153 | // }, err => reject(err)) 154 | // }) 155 | // } 156 | 157 | // checkFork (userKey, gitAccessToken, jobKey, ownerName, userName, issue) { 158 | // const checkFork = this.firebaseFunctions.httpsCallable(`app/checkFork`) 159 | 160 | // return new Promise((resolve, reject) => { 161 | // checkFork({ 162 | // userKey: userKey, 163 | // repoName: issue.repositoryName, 164 | // repoOwner: ownerName, 165 | // userName: userName, 166 | // gitAccessToken: gitAccessToken, 167 | // jobKey: jobKey 168 | // }).then(response => { 169 | // if (response.status !== 200 || parseInt(response.body.status) === 0) { 170 | // const message = `error starting work, err: ${response}` 171 | // reject(message) 172 | // } else { 173 | // resolve(response.body.value) 174 | // } 175 | // }, err => reject(err)) 176 | // }) 177 | // } 178 | } 179 | 180 | export default { 181 | install (Vue, options) { 182 | // const vueHttp = Vue.prototype.$http 183 | const service = new CloudFunctionService(options, Vue.http) 184 | Vue.prototype.$cloudFunction = service 185 | // Vue.$cloudFunction = service 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/services/etherscan-service.js: -------------------------------------------------------------------------------- 1 | class EtherscanService { 2 | constructor (config, httpClient) { 3 | this.config = config 4 | this.httpClient = httpClient 5 | } 6 | 7 | getContractLink (network, contract) { 8 | if (network && contract) { 9 | return network.startsWith('main') ? `https://etherscan.io/address/${contract}` : `https://${network}.etherscan.io/address/${contract}` 10 | } 11 | } 12 | 13 | getTransactionLink (network, transactionHash) { 14 | if (network && transactionHash) { 15 | return network.startsWith('main') ? `https://etherscan.io/tx/${transactionHash}` : `https://${network}.etherscan.io/tx/${transactionHash}` 16 | } 17 | } 18 | } 19 | 20 | export default { 21 | install (Vue, options) { 22 | // const vueHttp = Vue.prototype.$http 23 | const service = new EtherscanService(options, Vue.http) 24 | Vue.prototype.$etherscanService = service 25 | // Vue.$cloudFunction = service 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/services/github-public-service.js: -------------------------------------------------------------------------------- 1 | class GithubPublicService { 2 | constructor (config, httpClient) { 3 | this.apiUrl = config.apiUrl 4 | this.httpClient = httpClient 5 | this.userRepos = null 6 | } 7 | 8 | async httpGet (uri, gitAccessToken) { 9 | const headers = gitAccessToken ? { 'Authorization': `Bearer ${gitAccessToken}` } : {} 10 | const res = await this.httpClient.get(`${this.apiUrl}/${uri}`, { headers }) 11 | return res.body 12 | } 13 | 14 | async getRepoIssues (repositoryName) { 15 | return await this.httpGet(`repos/${repositoryName}/issues`) 16 | } 17 | 18 | async getUserRepos (gitAccessToken) { 19 | const url = `user/repos` 20 | if (!gitAccessToken) throw `missing gitAccessToken for ${url}` 21 | 22 | try { 23 | return await this.httpGet(url, gitAccessToken) 24 | } catch (error) { 25 | if (error.body && error.body.message && error.body.message === 'Not Found') { 26 | return null 27 | } else if (error.body && error.body.message && error.body.message === 'Requires authentication') { 28 | console.warn('getUserRepos Error, not authorized', error) 29 | return [] 30 | } else { 31 | console.error('getUserRepos Error', error) 32 | throw error 33 | } 34 | } 35 | } 36 | 37 | async getUserOrgs (gitAccessToken) { 38 | const url = `user/orgs` 39 | if (!gitAccessToken) throw `missing gitAccessToken for ${url}` 40 | try { 41 | return await this.httpGet(url, gitAccessToken) 42 | } catch (error) { 43 | if (error.body && error.body.message && error.body.message === 'Not Found') { 44 | return null 45 | } else { 46 | console.error('getUserOrgs Error') 47 | console.dir(error) 48 | 49 | throw error 50 | } 51 | } 52 | } 53 | 54 | async getOrgRepos (gitAccessToken, login) { 55 | const url = `orgs/${login}/repos` 56 | if (!gitAccessToken) throw `missing gitAccessToken for ${url}` 57 | 58 | try { 59 | return await this.httpGet(url, gitAccessToken) 60 | } catch (error) { 61 | if (error.body && error.body.message && error.body.message === 'Not Found') { 62 | return null 63 | } else if (error.body && error.body.message && error.body.message === 'Requires authentication') { 64 | console.warn('getOrgRepos Error, not authorized', error) 65 | return [] 66 | } else { 67 | console.error('getOrgRepos Error') 68 | console.dir(error) 69 | 70 | throw error 71 | } 72 | } 73 | } 74 | 75 | async getUserOwnedRepos (gitAccessToken) { 76 | if (!this.userRepos) { 77 | const orgs = await this.getUserOrgs(gitAccessToken) 78 | const promises = orgs 79 | .map(_ => this.getOrgRepos(gitAccessToken, _.login)) 80 | .concat(this.getUserRepos(gitAccessToken)) 81 | 82 | const res = await Promise.all(promises) 83 | const userRepos = res.reduce((a, c) => a.concat(c), []) 84 | this.userRepos = userRepos.filter(_ => !_.fork) 85 | } 86 | return this.userRepos 87 | } 88 | } 89 | 90 | export default { 91 | install (Vue, options) { 92 | // const vueHttp = Vue.prototype.$http 93 | Vue.prototype.$gitService = new GithubPublicService(options, Vue.http) 94 | // Vue.$cloudFunction = service 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/services/web3-service.js: -------------------------------------------------------------------------------- 1 | class Web3Service { 2 | constructor (config) { 3 | const factoryContractAbi = JSON.parse(config.factoryContractAbi) 4 | this.factoryContracts = { 5 | main: { abi: factoryContractAbi, address: config.mainnetFactoryContract }, 6 | rinkeby: { abi: factoryContractAbi, address: config.rinkebyFactoryContract } 7 | } 8 | } 9 | 10 | get networks () { 11 | return { 12 | '1': 'main', 13 | '4': 'rinkeby' 14 | } 15 | } 16 | 17 | async getCurrentAccount (web3) { 18 | const accounts = await web3.eth.getAccounts() 19 | return accounts[0] 20 | } 21 | 22 | async getCurrentNetwork (web3) { 23 | const network = await web3.eth.net.getNetworkType() 24 | return network.toLowerCase() 25 | } 26 | 27 | async sendCreateIssue (web3, user, repository, issue, bounty, transactionHashCallback, confirmCallBack, errorCallback) { 28 | const valueWei = web3.utils.toWei(bounty.toString(), 'ether') 29 | 30 | const account = await this.getCurrentAccount(web3) 31 | const network = await this.getCurrentNetwork(web3) 32 | const { abi, address } = this.factoryContracts[network] 33 | const contract = new web3.eth.Contract(abi, address) 34 | 35 | const receipt = await contract.methods.createIssue(user.toString(), repository.toString(), issue.toString()) 36 | .send({ from: account, value: valueWei }) 37 | .on('transactionHash', transactionHashCallback) 38 | // .on('confirmation', confirmCallBack) 39 | .on('error', errorCallback) 40 | 41 | // const { transactionHash, blockNumber } = receipt 42 | // const { contractAddress } = receipt.events.IssueCreated.returnValues 43 | // return { transactionHash, blockNumber, contractAddress, network } 44 | } 45 | } 46 | 47 | export default { 48 | install (Vue, options) { 49 | const service = new Web3Service(options) 50 | Vue.prototype.$web3Service = service 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: {}, 8 | mutations: {}, 9 | actions: {} 10 | }) 11 | -------------------------------------------------------------------------------- /src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | 2 | /* theme color */ 3 | $--color-primary: teal; 4 | 5 | /* icon font path, required */ 6 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 7 | 8 | @import "~element-ui/packages/theme-chalk/src/index"; -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import '~reset-css/reset.css'; 2 | @import "theme"; 3 | 4 | $footerHeight: 32px; 5 | $dark: #280911; 6 | $highlight: #E26058; 7 | $border: #E8EBEC; 8 | 9 | 10 | html { 11 | font-family: 'Montserrat', sans-serif; 12 | font-size: 18px; 13 | color: $dark; 14 | } 15 | 16 | h1 { 17 | font-size: 1.2rem; 18 | font-weight: bold; 19 | margin-bottom: 1.8rem; 20 | } 21 | 22 | .container { 23 | max-width: 85%; 24 | margin: 0 auto; 25 | } 26 | 27 | .el-tag-net { 28 | color: #409eff; 29 | text-transform: capitalize; 30 | // font-weight: bold; 31 | } 32 | 33 | .badge-hint { 34 | cursor: pointer; 35 | } 36 | 37 | // .el-badge__content--info { 38 | // background-color: #878d99 !important 39 | // } 40 | 41 | .el-tag-single { 42 | margin-right: 0px !important 43 | } 44 | 45 | .el-tag { 46 | margin-right: 5px 47 | } 48 | 49 | .section { 50 | padding: 1rem 0; 51 | } 52 | 53 | // GM Header 54 | .gm-header { 55 | background: $dark; 56 | color: white; 57 | display: flex; 58 | height: 92px; 59 | 60 | &__logo { 61 | flex: 0 0 20%; 62 | background: url(assets/logo.svg) (10px center)/contain no-repeat; 63 | position: relative; 64 | top: 10px; 65 | } 66 | 67 | &__content { 68 | flex: 0 1 100%; 69 | } 70 | 71 | &__user { 72 | flex: 0 0 20%; 73 | text-align: right; 74 | margin-right: 20px; 75 | display: flex; 76 | align-items: center; 77 | justify-content: flex-end; 78 | cursor: pointer; 79 | } 80 | } 81 | 82 | // Triple item 83 | .triple-item { 84 | display: flex; 85 | align-items: center; 86 | height: 100%; 87 | 88 | &__item { 89 | flex: 0 1 100%; 90 | text-align: center; 91 | display: flex; 92 | align-items: center; 93 | align-self: stretch; 94 | justify-content: center; 95 | border-bottom: 4px solid transparent; 96 | cursor: pointer; 97 | transition: all 0.2s; 98 | color: white; 99 | text-decoration: none; 100 | 101 | &:hover { 102 | border-color: lighten($highlight, 10%); 103 | } 104 | 105 | &.router-link-active, &--active { 106 | color: $highlight; 107 | border-color: $highlight; 108 | } 109 | } 110 | } 111 | 112 | #about { 113 | border: none; 114 | } 115 | 116 | // Circle item 117 | .circle-item { 118 | display: flex; 119 | align-items: center; 120 | &__circle { 121 | width: 30px; 122 | height: 30px; 123 | background: white 50%/contain no-repeat; 124 | border-radius: 100%; 125 | } 126 | &__text { 127 | font-size: 0.85rem; 128 | padding-left: 0.5rem; 129 | color: white; 130 | } 131 | } 132 | 133 | // Repo item 134 | .repo-item { 135 | display: flex; 136 | align-items: center; 137 | padding: 1rem 0; 138 | border-bottom: 1px solid $border; 139 | justify-content: space-between; 140 | 141 | &__details { 142 | .el-tag { 143 | margin-right: 1rem; 144 | } 145 | } 146 | } 147 | 148 | // Logo large 149 | .logo-large { 150 | width: 271px; 151 | height: 396px; 152 | background: url(assets/logo-large.svg) 50%/contain no-repeat; 153 | } 154 | 155 | .gm-footer { 156 | height: $footerHeight; 157 | background-color: $dark; 158 | width: 100%; 159 | 160 | &__content { 161 | padding-top: 10px; 162 | font-size: 12px; 163 | text-align: center; 164 | } 165 | } 166 | 167 | .container.section { 168 | min-height: 500px; 169 | } 170 | 171 | .error-login { 172 | white-space: unset !important; 173 | } 174 | 175 | .button-login { 176 | width: 441px; 177 | height: 99px; 178 | background: url(assets/button-login.svg) 50%/contain no-repeat; 179 | font-size: 0px; 180 | cursor: pointer; 181 | transition: all 0.2s; 182 | 183 | &:hover { 184 | opacity: 0.7; 185 | } 186 | } 187 | 188 | .login-container { 189 | justify-content: center; 190 | min-height: 100vh; 191 | display: flex; 192 | align-items: center; 193 | 194 | &__content { 195 | display: flex; 196 | flex-direction: column; 197 | align-items: center; 198 | } 199 | 200 | .logo-large { 201 | margin-bottom: 2rem; 202 | } 203 | } 204 | 205 | // Craigs styles 206 | 207 | .modal-mask { 208 | position: fixed; 209 | z-index: 9998; 210 | top: 0; 211 | left: 0; 212 | width: 100%; 213 | height: 100%; 214 | background-color: rgba(0, 0, 0, .5); 215 | display: table; 216 | transition: opacity .3s ease; 217 | } 218 | 219 | .modal-wrapper { 220 | display: table-cell; 221 | vertical-align: middle; 222 | } 223 | 224 | .modal-container-large { 225 | width: 700px; 226 | margin: 0px auto; 227 | font-size: 12px; 228 | padding: 20px 30px; 229 | background-color: #fff; 230 | border-radius: 2px; 231 | box-shadow: 0 2px 8px rgba(0, 0, 0, .33); 232 | transition: all .3s ease; 233 | font-family: Helvetica, Arial, sans-serif; 234 | } 235 | 236 | code { 237 | font-family: monospace; 238 | font-size: 11px !important; 239 | padding: 10px !important; 240 | } 241 | 242 | .modal-container-small { 243 | margin: 0px auto; 244 | width: 300px; 245 | font-size: 12px; 246 | padding: 20px 30px; 247 | background-color: #fff; 248 | border-radius: 2px; 249 | box-shadow: 0 2px 8px rgba(0, 0, 0, .33); 250 | transition: all .3s ease; 251 | font-family: Helvetica, Arial, sans-serif; 252 | } 253 | 254 | .modal-header h3 { 255 | margin-top: 0; 256 | color: #42b983; 257 | } 258 | 259 | .modal-body { 260 | margin: 20px 0; 261 | } 262 | 263 | .modal-default-button { 264 | float: right; 265 | } 266 | 267 | /* 268 | * The following styles are auto-applied to elements with 269 | * transition="modal" when their visibility is toggled 270 | * by Vue.js. 271 | * 272 | * You can easily play with the modal transition by editing 273 | * these styles. 274 | */ 275 | 276 | .modal-enter { 277 | opacity: 0; 278 | } 279 | 280 | .modal-leave-active { 281 | opacity: 0; 282 | } 283 | 284 | .modal-enter .modal-container, 285 | .modal-leave-active .modal-container { 286 | -webkit-transform: scale(1.1); 287 | transform: scale(1.1); 288 | } 289 | 290 | .tip { 291 | padding: 12px 24px 12px 30px; 292 | margin: 2em 0; 293 | height: 50px; 294 | border-left: 4px solid #f66; 295 | background-color: #f9f9f9; 296 | position: relative; 297 | border-top-left-radius: 0px; 298 | border-top-right-radius: 0px; 299 | box-shadow: 0 1px 1px rgba(0,0,0,0.125); 300 | } 301 | 302 | .tip:before { 303 | position: absolute; 304 | top: 14px; 305 | left: -12px; 306 | color: #fff; 307 | content: "!"; 308 | width: 20px; 309 | height: 20px; 310 | border-radius: 100%; 311 | text-align: center; 312 | line-height: 20px; 313 | font-weight: bold; 314 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 315 | font-size: 14px; 316 | } 317 | 318 | .tip-small 319 | { 320 | font-size: 12px; 321 | padding: 12px 24px 12px 30px; 322 | //border-left: 4px solid #f66; 323 | background-color: #f9f9f9; 324 | position: relative; 325 | border-top-left-radius: 0px; 326 | border-top-right-radius: 0px; 327 | box-shadow: 0 1px 1px rgba(0,0,0,0.125); 328 | } 329 | 330 | .tip-small:before { 331 | position: absolute; 332 | top: 9px; 333 | left: -12px; 334 | color: #fff; 335 | //content: "!"; 336 | width: 20px; 337 | height: 20px; 338 | border-radius: 100%; 339 | text-align: center; 340 | line-height: 20px; 341 | font-weight: bold; 342 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 343 | font-size: 14px; 344 | } 345 | 346 | .tip-how-to { 347 | padding: 8px 16px; 348 | border-radius: 4px; 349 | border-left: 5px solid #E26058; 350 | margin: 20px 0; 351 | font-size: 14px; 352 | color: black; 353 | line-height: 1.5em; 354 | } 355 | 356 | //Issue Cards 357 | 358 | .text { 359 | font-size: 14px; 360 | } 361 | 362 | .item { 363 | float: right; 364 | } 365 | 366 | .clearfix:before, 367 | .clearfix:after { 368 | display: table; 369 | content: ""; 370 | } 371 | .clearfix:after { 372 | clear: both 373 | } 374 | 375 | .box-card { 376 | position: relative; 377 | width: 100%; 378 | margin-bottom: 30px; 379 | border-top-width: 2px; 380 | } 381 | 382 | .card-button { 383 | float: right; 384 | padding: 3px 0; 385 | 386 | } 387 | 388 | //random 389 | h3 { 390 | display: block; 391 | font-size: 1.17em; 392 | -webkit-margin-before: 1em; 393 | -webkit-margin-after: 1em; 394 | -webkit-margin-start: 0px; 395 | -webkit-margin-end: 0px; 396 | } 397 | 398 | .small-bold { 399 | font-weight: bold; 400 | font-size: 12px; 401 | } 402 | 403 | .bold { 404 | font-weight: bold; 405 | font-size: 12px; 406 | } 407 | 408 | .bold-how-to { 409 | font-weight: bold; 410 | } 411 | 412 | //repo cards 413 | 414 | .card-button-repo { 415 | float: right; 416 | margin-right: 0.4rem; 417 | margin-top: -0.4rem; 418 | } 419 | 420 | .refresh-button { 421 | float: right; 422 | margin-left:5px; 423 | cursor: pointer; 424 | } 425 | 426 | .box-card-repo { 427 | width: 100%; 428 | padding: 10px 2px 10px 2px; 429 | margin-bottom: 30px; 430 | } 431 | 432 | .float-right { 433 | float: right; 434 | } 435 | 436 | .float-left { 437 | float: left; 438 | } 439 | 440 | .wallet-text { 441 | font-size: 13px; 442 | } 443 | 444 | .el-select-full { 445 | display: inline; 446 | } 447 | 448 | .el-select-dropdown { 449 | z-index: 9999 !important; 450 | } 451 | 452 | .bottom-pad-medium { 453 | padding-bottom: 20px; 454 | } 455 | 456 | .top-pad-5 { 457 | padding-top: 5px; 458 | } 459 | 460 | .right-pad-5 { 461 | padding-right: 5px; 462 | } 463 | 464 | .margin-left-3 { 465 | margin-left: 3px; 466 | } 467 | 468 | .left-pad-5 { 469 | padding-left: 5px; 470 | } 471 | 472 | .bottom-pad-xs { 473 | padding-bottom: 5px; 474 | } 475 | 476 | .top-margin-medium { 477 | margin-top:10px; 478 | } 479 | 480 | .pointer { 481 | cursor: pointer; 482 | } 483 | 484 | .pad-deposit-spinner { 485 | padding-bottom: 20px; 486 | } 487 | 488 | .test-network { 489 | width:280px; 490 | text-align: center; 491 | font-size: 14px; 492 | color: black; 493 | padding: 10px; 494 | } 495 | 496 | .full-width { 497 | width: 100%; 498 | } 499 | 500 | .rinkeby-width { 501 | width: 40%; 502 | } 503 | 504 | .telegram { 505 | height:20px; 506 | margin-left: 35px; 507 | vertical-align: middle; 508 | text-align: center; 509 | 510 | } 511 | 512 | .youtube { 513 | height:18px; 514 | margin-left: 32px; 515 | vertical-align: middle; 516 | text-align: center; 517 | 518 | } 519 | 520 | .gitbox { 521 | width: 75%; 522 | } 523 | 524 | .depositbox { 525 | width: 55%; 526 | } 527 | 528 | .el-dropdown-link { 529 | cursor: pointer; 530 | font-size: 18px; 531 | color: #E26058; 532 | } 533 | 534 | .requiredmessage { 535 | display: block; 536 | color: #f57f6c; 537 | font-size: 12px; 538 | } 539 | 540 | a { 541 | color: rgb(252, 107, 99); 542 | text-decoration: none; 543 | } 544 | 545 | .el-row { 546 | margin-bottom: 20px; 547 | &:last-child { 548 | margin-bottom: 0; 549 | } 550 | } 551 | .el-col { 552 | border-radius: 4px; 553 | } 554 | 555 | .card-action-footer { 556 | padding-top: 20px; 557 | padding-bottom: 20px; 558 | position: absolute; 559 | bottom: 10px; 560 | width: 100%; 561 | text-align: center; 562 | 563 | } 564 | 565 | .repo-action-footer { 566 | padding-top: 20px; 567 | bottom: 10px; 568 | position: block; 569 | text-align: center; 570 | } 571 | 572 | 573 | .tiny-text { 574 | font-size: 10px; 575 | text-align: center; 576 | margin-top: 5px; 577 | color: #707070; 578 | 579 | } 580 | 581 | .medium-text { 582 | font-size: 12px; 583 | text-align: center; 584 | } 585 | 586 | .large-text { 587 | font-size: 14px; 588 | text-align: center; 589 | } 590 | 591 | .xlarge-text { 592 | font-size: 18px; 593 | text-align: center; 594 | } 595 | 596 | .issue-title { 597 | font-size: 14px; 598 | font-weight: bold; 599 | } 600 | 601 | .issue-description { 602 | height: 2.5em; 603 | } 604 | 605 | .earn-padding { 606 | padding-top:30px; 607 | } 608 | 609 | @keyframes animate { 610 | 0% { 611 | transform:scaleX(0); 612 | transform-origin: left; 613 | } 614 | 50% 615 | { 616 | transform:scaleX(1); 617 | transform-origin: left; 618 | } 619 | 50.1% 620 | { 621 | transform:scaleX(1); 622 | transform-origin: right; 623 | } 624 | 100% 625 | { 626 | transform:scaleX(0); 627 | transform-origin: right; 628 | } 629 | } 630 | 631 | .badge-inline 632 | { 633 | padding-left: 5px 634 | } 635 | 636 | .card-red-top { 637 | position: relative; 638 | border-radius: 4px; 639 | border-top: 5px solid #E26058; 640 | } 641 | 642 | .card-section { 643 | padding: 10px 20px !important; 644 | white-space: nowrap; 645 | overflow: hidden; 646 | text-overflow: ellipsis; 647 | } 648 | 649 | .card-section .card-text { 650 | white-space: nowrap; 651 | overflow: hidden; 652 | text-overflow: ellipsis; 653 | } 654 | 655 | /* 656 | .card-section .card-text:hover{ 657 | overflow: visible; 658 | white-space: normal; 659 | height:auto; 660 | } 661 | */ 662 | /* 663 | .card-section:hover{ 664 | overflow: visible; 665 | white-space: normal; 666 | height:auto; 667 | } 668 | */ 669 | .card-text .text-repo-big { 670 | width: 250px; 671 | white-space: nowrap; 672 | overflow: hidden; 673 | text-overflow: ellipsis; 674 | } 675 | 676 | .card-text .text-repo-big:hover{ 677 | overflow: visible; 678 | white-space: normal; 679 | height:auto; /* just added this line */ 680 | } 681 | 682 | .divider { 683 | border-top: 1px solid #e6ebf5; 684 | } 685 | 686 | .card-primary { 687 | position: relative; 688 | border-radius: 4px; 689 | border-top: 5px solid rgba(64, 158, 255, 0.2); 690 | } 691 | 692 | .card-info { 693 | position: relative; 694 | border-radius: 4px; 695 | border-top: 5px solid hsla(220,4%,58%,.1); 696 | } 697 | 698 | .card-success { 699 | position: relative; 700 | border-radius: 4px; 701 | border-top: 5px solid rgba(103,194,58,.2) 702 | } 703 | 704 | .card-danger { 705 | position: relative; 706 | border-radius: 4px; 707 | border-top: 5px solid hsla(0,87%,69%,.2) 708 | } 709 | 710 | .card-warning { 711 | position: relative; 712 | border-radius: 4px; 713 | border-top: 5px solid rgba(230,162,60,.2) 714 | } 715 | 716 | .card-small { 717 | height: 180px; 718 | } 719 | 720 | .card-big { 721 | height: 320px; 722 | } 723 | 724 | // .card-warning-progress { 725 | 726 | // position: relative; 727 | // height: 290px; 728 | // border-radius: 4px; 729 | // border-top: 0px solid rgba(230,162,60,.2); 730 | // transform:rotate(0deg); 731 | // box-sizing: border-box; 732 | // } 733 | 734 | 735 | // .card-warning:before 736 | // { 737 | 738 | // height: 280px; 739 | // content: ''; 740 | // z-index: 1; 741 | // position: absolute; 742 | // width:100%; 743 | // height: 5px; 744 | // background: rgba(235, 158, 5, 0.2);; 745 | // animation: animate 6s linear infinite; 746 | // overflow: hidden; 747 | 748 | // } 749 | 750 | 751 | 752 | 753 | 754 | .dev-count { 755 | position: absolute; 756 | bottom: 5px; 757 | text-align: center; 758 | width: 100%; 759 | left: 0px; 760 | } 761 | 762 | .text-center 763 | { 764 | text-align: center; 765 | } 766 | 767 | .tooltip-inner { 768 | border: 1px solid #e6ebf5; 769 | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); 770 | background-color: #fff; 771 | margin: 10px; 772 | font-family: 'Montserrat', sans-serif; 773 | font-size: 14px; 774 | width: 250px; 775 | padding: 0.25rem 0.5rem; 776 | color: #2d2f33; 777 | text-align: center; 778 | border-radius: 0.25rem; 779 | } 780 | 781 | .tooltip { 782 | position: absolute; 783 | z-index: 1070; 784 | display: block; 785 | margin: 0; 786 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 787 | font-style: normal; 788 | font-weight: 400; 789 | line-height: 1.5; 790 | text-align: left; 791 | text-align: start; 792 | text-decoration: none; 793 | text-shadow: none; 794 | text-transform: none; 795 | letter-spacing: normal; 796 | word-break: normal; 797 | word-spacing: normal; 798 | white-space: normal; 799 | line-break: auto; 800 | font-size: 0.875rem; 801 | word-wrap: break-word; 802 | opacity: 0; 803 | } 804 | 805 | .tooltip.show { 806 | opacity: 1; 807 | } 808 | 809 | .tooltip .arrow { 810 | position: absolute; 811 | display: block; 812 | width: 0.8rem; 813 | height: 0.4rem; 814 | } 815 | 816 | .tooltip .arrow::before { 817 | position: absolute; 818 | content: ""; 819 | border-color: transparent; 820 | border-style: solid; 821 | } 822 | 823 | .bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { 824 | padding: 0.4rem 0; 825 | } 826 | 827 | .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { 828 | bottom: 0; 829 | } 830 | 831 | .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { 832 | top: 0; 833 | border-width: 0.4rem 0.4rem 0; 834 | border-top-color: #000; 835 | } 836 | 837 | .fade { 838 | transition: opacity 0.15s linear; 839 | } 840 | 841 | @media screen and (prefers-reduced-motion: reduce) { 842 | .fade { 843 | transition: none; 844 | } 845 | } 846 | 847 | .fade:not(.show) { 848 | opacity: 0; 849 | } 850 | 851 | .earn-eth-padding { 852 | margin-right: 20px; 853 | } 854 | 855 | .post-job-button-width { 856 | width: 130px; 857 | } 858 | 859 | .manual-transfer-button-width { 860 | width: 160px; 861 | } 862 | 863 | .welcome-heading { 864 | font-size: 35px; 865 | } 866 | 867 | .stepper-heading { 868 | font-size: 25px; 869 | } 870 | 871 | .modal-heading { 872 | font-size: 20px; 873 | font-weight: bold; 874 | text-align: center; 875 | } 876 | 877 | .modal-description { 878 | font-size: 12px; 879 | text-align: center; 880 | margin-top: 1em; 881 | } 882 | 883 | .welcome-margin { 884 | margin-top: 80px; 885 | } 886 | 887 | .gm-header-welcome { 888 | background: $dark; 889 | color: white; 890 | height: 150px; 891 | display:block; 892 | margin:auto; 893 | background: url(assets/logo.svg) (10px center)/contain no-repeat; 894 | position: relative; 895 | top: 30px; 896 | left:44%; 897 | } 898 | 899 | .gm-user-welcome{ 900 | flex: 0 0 20%; 901 | padding-left: 40%; 902 | margin-right: 20px; 903 | align-items: center; 904 | justify-content: flex-end; 905 | cursor: pointer; 906 | color: black; 907 | } 908 | 909 | .circle-item-welcome { 910 | display: flex; 911 | align-items: center; 912 | &__circle { 913 | width: 30px; 914 | height: 30px; 915 | background: white 50%/contain no-repeat; 916 | border-radius: 100%; 917 | } 918 | &__text { 919 | font-size: 0.85rem; 920 | padding-left: 0.5rem; 921 | color: black; 922 | } 923 | } 924 | 925 | .eth-dev-wallet{ 926 | width: 50%; 927 | margin-left: 0px; 928 | } 929 | 930 | .pad-right { 931 | padding-right: 1rem; 932 | } 933 | 934 | /** Fix for step heading not centering under step number. **/ 935 | .is-horizontal .el-step__title { 936 | position: relative; 937 | right: 0.6rem; 938 | } 939 | 940 | .filter-switch { 941 | padding-top: 10px; 942 | } 943 | 944 | .section-heading { 945 | font-weight: bold; 946 | } 947 | 948 | .section-info { 949 | font-size: 0.8em; 950 | color: #707070; 951 | margin-top: 5px; 952 | } 953 | 954 | .el-tag--warning > a{ 955 | color: inherit; 956 | } 957 | 958 | .el-tag--success > a{ 959 | color: inherit; 960 | } 961 | 962 | .el-badge--info { 963 | color: inherit; 964 | } 965 | 966 | .eth-balance > a 967 | { 968 | font-size: 1.15em; 969 | font-weight: bold; 970 | color: black !important; 971 | vertical-align: middle; 972 | } 973 | 974 | 975 | .card-text-muted { 976 | font-size: 0.8em; 977 | color: #707070 978 | } 979 | 980 | .text-repo { 981 | font-size: 0.8em; 982 | color: #707070; 983 | padding-top: 5px; 984 | margin-top: 5px; 985 | 986 | } 987 | 988 | .text-repo > svg { 989 | font-size: inherit; 990 | color: inherit; 991 | margin-right: 5px; 992 | } 993 | 994 | .text-user { 995 | font-size: 0.8em; 996 | color: #707070; 997 | padding-top: 5px; 998 | } 999 | 1000 | .text-user > svg { 1001 | font-size: inherit; 1002 | color: inherit; 1003 | margin-right: 5px; 1004 | } 1005 | 1006 | .snackbar__action { 1007 | font-size: 0.8em !important; 1008 | } 1009 | 1010 | .snackbar__text { 1011 | font-size: 0.8em !important; 1012 | } 1013 | 1014 | 1015 | 1016 | 1017 | .text-repo-big { 1018 | vertical-align: baseline; 1019 | font-size: 1em; 1020 | color: black; 1021 | padding-top: 5px; 1022 | } 1023 | 1024 | .text-repo-big > svg, a { 1025 | color: inherit; 1026 | margin-right: 5px; 1027 | } 1028 | 1029 | 1030 | 1031 | .depositButton{ 1032 | padding-top: 20px; 1033 | } 1034 | 1035 | .privateLock { 1036 | padding-right: 5px; 1037 | } 1038 | 1039 | .el-tooltip-wrapper { 1040 | display: inline-block; 1041 | padding: 0; 1042 | margin: 0; 1043 | border: none; 1044 | } 1045 | 1046 | .gitCommands { 1047 | width: 700px; 1048 | } 1049 | 1050 | .heading { 1051 | font-weight: bold; 1052 | font-size: 18px; 1053 | text-align: center; 1054 | margin-bottom: 14px; 1055 | } 1056 | 1057 | .sub-heading { 1058 | font-weight: bold; 1059 | font-size: 16px; 1060 | margin-top: 14px; 1061 | } 1062 | 1063 | .about-description { 1064 | font-weight: normal; 1065 | font-size: 14px; 1066 | text-align: left; 1067 | line-height: 2; 1068 | } 1069 | 1070 | .deposit-description { 1071 | font-weight: normal; 1072 | font-size: 14px; 1073 | text-align: left; 1074 | line-height: 2; 1075 | } 1076 | 1077 | pre code { 1078 | font-size: 10px; 1079 | background-color: #eee; 1080 | border: 1px solid #999; 1081 | display: block; 1082 | padding: 20px; 1083 | } 1084 | 1085 | .networkHeadingWelcome { 1086 | margin: 20px; 1087 | } 1088 | 1089 | .deposit-check-text { 1090 | font-size: 10px; 1091 | padding-top: 10px; 1092 | } 1093 | -------------------------------------------------------------------------------- /src/styles/sidebar/var.scss: -------------------------------------------------------------------------------- 1 | $primaryColor: #4285f4 !default; 2 | $baseBg: #2a2a2e !default; 3 | $darkenBg: darken( $baseBg, 5% ) !default; 4 | $lightenBg: lighten( $baseBg, 5% ) !default; 5 | 6 | $itemColor: #fff !default; 7 | 8 | $itemOpenColor: #fff !default; 9 | $itemOpenBg: $primaryColor !default; 10 | 11 | $itemHoverColor: #fff !default; 12 | $itemHoverBg: rgba($darkenBg, 0.5) !default; 13 | 14 | $iconColor: #fff !default; 15 | $iconBg: $darkenBg !default; 16 | 17 | $iconActiveColor: #fff !default; 18 | $iconActiveBg: $darkenBg !default; 19 | 20 | $iconOpenColor: #fff !default; 21 | $iconOpenBg: transparent !default; 22 | 23 | $mobileItemColor: #fff !default; 24 | $mobileItemBg: $primaryColor !default; 25 | $mobileIconBg: transparent !default; 26 | $mobileIconColor: #fff !default; 27 | 28 | $dropDownColor: #fff !default; 29 | $dropDownBg: $lightenBg !default; -------------------------------------------------------------------------------- /src/styles/sidebar/vue-sidebar-menu.scss: -------------------------------------------------------------------------------- 1 | @import './var.scss'; 2 | .v-sidebar-menu { 3 | & , * { 4 | box-sizing: border-box; 5 | } 6 | & { 7 | position: fixed; 8 | // top: 30; 9 | left: 0; 10 | width: 100%; 11 | height: 80vh; 12 | padding-bottom: 50px; 13 | z-index: 999; 14 | transition: 0.3s width; 15 | &.rtl { 16 | right: 0; 17 | left: inherit; 18 | text-align: right; 19 | } 20 | & > .vsm-list { 21 | opacity: 0.7 !important; 22 | width: 100%; 23 | height: 100%; 24 | overflow: hidden auto; 25 | } 26 | &.vsm-collapsed > .vsm-list { 27 | width: calc(100% + 17px); 28 | padding-right: 17px; 29 | } 30 | &.rtl > .vsm-list { 31 | direction: rtl; 32 | } 33 | &.vsm-collapsed.rtl > .vsm-list { 34 | padding-right: 0px; 35 | padding-left: 17px; 36 | float: right; 37 | } 38 | } 39 | & .vsm-item { 40 | position: relative; 41 | display: block; 42 | } 43 | & .vsm-item.first-item { 44 | & > .vsm-link { 45 | & > .vsm-icon { 46 | height: 30px; 47 | line-height: 30px; 48 | width: 30px; 49 | text-align: center; 50 | border-radius: 3px; 51 | } 52 | &:after { 53 | content: ''; 54 | display: block; 55 | clear: both; 56 | } 57 | } 58 | } 59 | & .vsm-item.mobile-item { 60 | & > .vsm-link { 61 | & > .vsm-icon { 62 | height: 30px; 63 | line-height: 30px; 64 | width: 30px; 65 | text-align: center; 66 | border-radius: 3px; 67 | } 68 | } 69 | } 70 | & .vsm-item.active-item, .vsm-item.parent-active-item { 71 | & > .vsm-link { 72 | font-weight: 600; 73 | } 74 | } 75 | & .vsm-link { 76 | position: relative; 77 | display: block; 78 | font-size: 16px; 79 | font-weight: 400; 80 | padding: 10px; 81 | line-height: 30px; 82 | text-decoration: none; 83 | z-index: 20; 84 | transition: 0.3s all; 85 | &[disabled] { 86 | opacity: 0.4; 87 | pointer-events: none; 88 | } 89 | } 90 | & .vsm-title { 91 | display: block; 92 | white-space: nowrap; 93 | } 94 | & .vsm-icon { 95 | float: left; 96 | line-height: 30px; 97 | margin-right: 10px; 98 | } 99 | &.rtl .vsm-icon { 100 | float: right; 101 | margin-left: 10px; 102 | margin-right: 0px; 103 | } 104 | & .vsm-arrow { 105 | width: 30px; 106 | text-align: center; 107 | font-style: normal; 108 | font-weight: 900; 109 | position: absolute; 110 | right: 10px; 111 | top: 50%; 112 | transform: translateY(-50%); 113 | transition: 0.3s transform; 114 | &:after { 115 | content: '\f105'; 116 | font-family: 'Font Awesome 5 Free'; 117 | } 118 | &.open-arrow { 119 | transform: translateY(-50%) rotate(90deg); 120 | } 121 | } 122 | &.rtl .vsm-arrow { 123 | left: 10px; 124 | right: inherit; 125 | } 126 | & .vsm-dropdown > .vsm-list { 127 | 128 | padding: 5px; 129 | } 130 | & .expand-enter-active, 131 | & .expand-leave-active { 132 | transition: height 0.35s ease; 133 | overflow: hidden; 134 | } 135 | & .expand-enter, 136 | & .expand-leave-to { 137 | height: 0 !important; 138 | } 139 | & .slide-animation-enter-active { 140 | animation: slide-animation 0.2s; 141 | } 142 | & .slide-animation-leave-active { 143 | animation: slide-animation 0.2s reverse; 144 | } 145 | @keyframes slide-animation { 146 | 0% { 147 | width: 0%; 148 | } 149 | 100% { 150 | width: 100%; 151 | } 152 | } 153 | & .vsm-header { 154 | font-size: 14px; 155 | font-weight: 600; 156 | padding: 10px; 157 | white-space: nowrap; 158 | text-transform: uppercase; 159 | } 160 | & .vsm-badge { 161 | padding: 0px 6px; 162 | font-size: 12px; 163 | border-radius: 3px; 164 | position: absolute; 165 | right: 10px; 166 | height: 20px; 167 | line-height: 20px; 168 | margin-top: 5px; 169 | font-weight: 600; 170 | text-transform: uppercase; 171 | } 172 | &.rtl .vsm-badge { 173 | left: 10px; 174 | right: inherit; 175 | } 176 | & .collapse-btn { 177 | display: block; 178 | text-align: center; 179 | font-style: normal; 180 | font-weight: 900; 181 | position: absolute; 182 | height: 50px; 183 | left: 0; 184 | right: 0; 185 | bottom: 0; 186 | cursor: pointer; 187 | border: none; 188 | width: 100%; 189 | &:after { 190 | content: '\f337'; 191 | font-family: 'Font Awesome 5 Free'; 192 | } 193 | } 194 | } 195 | 196 | @import './white-theme.scss'; -------------------------------------------------------------------------------- /src/styles/sidebar/white-theme.scss: -------------------------------------------------------------------------------- 1 | $primaryColor: #4285f4; 2 | $baseBg: #fff; 3 | $darkenBg: darken( $baseBg, 5% ); 4 | $lightenBg: #e0e0e0; 5 | 6 | $itemColor: #262626; 7 | 8 | $itemOpenColor: #fff; 9 | $itemOpenBg: $primaryColor; 10 | 11 | $itemHoverColor: #262626; 12 | $itemHoverBg: $darkenBg; 13 | 14 | $iconColor: #262626; 15 | $iconBg: #bbc5d6; 16 | 17 | $iconActiveColor: #fff; 18 | $iconActiveBg: #262626; 19 | 20 | $iconOpenColor: #fff; 21 | $iconOpenBg: transparent; 22 | 23 | $mobileItemColor: #fff; 24 | $mobileItemBg: $primaryColor; 25 | $mobileIconBg: transparent; 26 | $mobileIconColor: #fff; 27 | 28 | $dropDownColor: #262626; 29 | $dropDownBg: #e3e3e3; 30 | 31 | .v-sidebar-menu.white-theme { 32 | background-color: $baseBg; 33 | & .vsm-link { 34 | color: $itemColor; 35 | } 36 | & .vsm-item.mobile-item { 37 | & > .vsm-link { 38 | color: $mobileItemColor; 39 | } 40 | & > .vsm-icon { 41 | color: $mobileIconColor; 42 | background-color: $mobileIconBg; 43 | } 44 | } 45 | & .vsm-item.first-item { 46 | & > .vsm-link { 47 | & > .vsm-icon { 48 | color: $iconColor; 49 | background-color: $iconBg; 50 | } 51 | } 52 | &.active-item > .vsm-link, &.parent-active-item > .vsm-link { 53 | box-shadow: 3px 0px 0px 0px $primaryColor inset; 54 | & > .vsm-icon { 55 | color: $iconActiveColor; 56 | background-color: $iconActiveBg; 57 | } 58 | } 59 | } 60 | &.rtl .vsm-item.first-item { 61 | &.active-item > .vsm-link, &.parent-active-item > .vsm-link { 62 | box-shadow: -3px 0px 0px 0px $primaryColor inset; 63 | } 64 | } 65 | &.vsm-default { 66 | & .vsm-item.first-item { 67 | &.open-item > .vsm-link { 68 | color: $itemOpenColor; 69 | background-color: $itemOpenBg; 70 | & > .vsm-icon { 71 | color: $iconOpenColor; 72 | background-color: $iconOpenBg; 73 | } 74 | } 75 | } 76 | & .vsm-link:hover { 77 | color: $itemHoverColor; 78 | background-color: $itemHoverBg; 79 | } 80 | } 81 | & .vsm-dropdown { 82 | & > .vsm-list { 83 | background-color: $dropDownBg; 84 | & .vsm-link { 85 | color: $dropDownColor; 86 | } 87 | & .vsm-link:hover { 88 | color: $itemHoverColor; 89 | background-color: $itemHoverBg; 90 | } 91 | } 92 | } 93 | & .vsm-mobile-bg { 94 | background-color: $mobileItemBg; 95 | } 96 | & .vsm-header { 97 | color: rgba($itemColor, 0.7); 98 | } 99 | & .vsm-badge.default-badge { 100 | color: $itemColor; 101 | background-color: $lightenBg; 102 | } 103 | & .collapse-btn { 104 | color: $itemColor; 105 | background-color: $darkenBg; 106 | } 107 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | Array.prototype.unique = function (keyExtractor) { 2 | return this.reduce((a, c) => a.map(x => keyExtractor ? keyExtractor(x) : x).indexOf(keyExtractor ? keyExtractor(c) : c) >= 0 ? a : [...a, c], []) 3 | } 4 | Array.prototype.searchBy = function (fields, value) { 5 | return !value ? this : this.filter(_ => fields.reduce((a, c) => a || (_[c] && _[c].toLowerCase().match(value.toLowerCase())), false)) 6 | } 7 | 8 | export const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args) 9 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | 3 | module.exports = { 4 | css: { 5 | modules: true 6 | }, 7 | configureWebpack: config => { 8 | 9 | // fiddle with webpack here, if needed 10 | 11 | // console.debug(process.env) 12 | // if (process.env.NODE_ENV === 'production') { 13 | // process.env.config = require('./environments/config.env.pre') 14 | // } else { 15 | // process.env.config = require('./environments/config.env.dev') 16 | // } 17 | } 18 | } 19 | --------------------------------------------------------------------------------