├── .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 |
2 | #app(v-loading.fullscreen.lock='loading')
3 | router-view
4 |
5 |
6 |
70 |
71 |
74 |
75 |
80 |
--------------------------------------------------------------------------------
/src/assets/button-login.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/content/HelpDeveloper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Steps to earn your reward
4 |
5 |
6 |
Click Start Work
7 |
8 |
9 |
10 |
Provide an Ethereum wallet address
11 |
12 |
13 |
14 |
If you don't have a wallet this
linkshows you how to create one
15 |
16 |
17 |
18 |
Click create branch
19 |
20 |
21 |
22 |
Fix the issue on the branch that was created
23 |
24 |
25 |
26 |
When fixed push changes to Github and then click submit as pull request
27 |
28 |
29 |
30 |
Once the pull request is merged the reward will be sent to your wallet
31 |
32 |
33 |
34 |
Here is a
videothat shows the above steps
35 |
36 |
37 |
38 |
If you have any questions or feedback you can chat to us
here
39 |
40 |
41 |
42 |
43 |
80 |
--------------------------------------------------------------------------------
/src/components/content/HelpDonator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Steps to add a bounty to an issue
4 |
5 |
6 |
First you will need to enable a repository, click enable repository
7 |
8 |
9 |
10 |
You will need to have metamask installed, if you dont here is a
linkthat shows you how to install metamask
11 |
12 |
13 |
14 |
Click create issue or select bounty issue from the drop down to bounty an existing issue
15 |
16 |
17 |
18 |
Fill in the details, select the amount of Ethereum that will be the reward and click bounty issue
19 |
20 |
21 |
22 |
Metamask will ask you to confirm the transaction, confirm and then the funding process will begin
23 |
24 |
25 |
26 |
27 |
66 |
--------------------------------------------------------------------------------
/src/components/core/Icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
87 |
88 |
101 |
--------------------------------------------------------------------------------
/src/components/core/IssueBox.vue:
--------------------------------------------------------------------------------
1 |
2 | el-card.box-card.card-big.d-flex.flex-column.justify-content-between(v-loading.fullscreen.lock='loading' :element-loading-text="loadingText" :body-style="{ padding: '0px' }" :class="issue.contractNetwork === 'main' ? 'card-'+stateColor : 'card-primary'")
3 | div(slot='header')
4 | el-tooltip(v-if="issue.contractNetwork !== 'main'" effect="dark" content="The bounty for this issue is on the ethereum rinkeby network, which means no actual money will be awarded upon completion." placement="top-start")
5 | el-tag.el-tag-net(size="small") Rinkeby
6 |
7 | el-tooltip(effect="dark" content="Error creating the smart contract" placement="top-start")
8 | el-tag(v-if='issue.status === "Rejected"' :type="stateColor" size="small" ) Error
9 |
10 | el-tooltip(effect="dark" content="Currently setting up the smart contract" placement="top-start")
11 | el-tag(v-if='isFunding && !issue.contractDeployTransaction' :type="stateColor" size="small") Funding
12 |
13 | el-tooltip(effect="dark" content="Currently setting up the smart contract, click to view the transaction on etherscan" placement="top-start")
14 | el-tag(v-if='isFunding && issue.contractDeployTransaction' :type="stateColor" size="small")
15 | a(:href="deployTransactionUrl" target="_blank") Funding
16 |
17 | el-tooltip(effect="dark" content="Pull Request merged" placement="top-start")
18 | el-tag(v-if='issue.status === "Closed"' :type="stateColor" size="small" ) Completed
19 |
20 | el-tooltip(effect="dark" :content="'Issue has been funded with ' + formattedETH(issue.bounty) + ' ETH, click to view on etherscan'" placement="top-start")
21 | el-tag(v-if='issue.status === "Funded"' :type="stateColor" size="small" )
22 | a(:href="contractUrl" target="_blank") Funded
23 |
24 | el-tooltip(effect="dark" :content="formattedETH(issue.bounty) + ' ETH, click to view on etherscan'" placement="top-end")
25 | span.eth-balance.float-right(v-if="issue.bounty")
26 | a(:href="contractUrl" target="_blank") ${{formattedUSD(issue.bounty)}}
27 |
28 | div.card-section
29 | a.card-text(:href="issueUrl" target="_blank") {{issue.title}}
30 | br
31 | div.card-text.text-repo
32 | icon(icon-name="repo")
33 | a(:href="repoUrl" target="_blank") {{issue.repositoryName}}
34 |
35 | el-tooltip(effect="dark" content="The donator for this issue" placement="top-start")
36 | div.card-text.text-user
37 | icon(icon-name="gist-secret")
38 | span {{issue.ownerName}}
39 |
40 | div.card-section.divider(v-if="userJob && userJob.length > 0")
41 | a.card-text(:href="userJob.forkedBranchUrl" target="_blank") Your contribution
42 |
43 | el-tooltip(v-if="!userJob.pullRequestNumber" effect="dark" content="Click to see how to get started" placement="top-start")
44 | el-badge.mark.badge-hint(value="?" @click.native="showGitCommand")
45 |
46 | job-link(:job="userJob" :repository-name="issue.repositoryName")
47 |
48 | div.card-section.divider(v-if="sentJobs && sentJobs.length > 0 && !(userJob && userJob.length > 0)")
49 | div.card-text Pending contributions
50 | div(v-for='job in sentJobs' :key="job.developerId")
51 | job-link(:job="job" :repository-name="issue.repositoryName")
52 |
53 |
54 | div.card-action-footer(v-if='issue.status !== "Funded" && !issue.contractDeployTransaction && !issue.contractAddress && user.gitUserName === issue.ownerName')
55 | el-button(size="small" type="primary" @click="cancelIssue") Cancel issue
56 |
57 | div.card-action-footer(v-if='issue.status !== "Funded" && issue.contractDeployTransaction && !issue.contractAddress && user.gitUserName === issue.ownerName')
58 | el-button(size="small" type="primary" @click="openTransaction(issue.contractNetwork, issue.contractDeployTransaction)") View transaction
59 |
60 |
61 | div.card-action-footer(v-if="issue.status !== 'Closed' && !userJob && issue.contractAddress && user && user.gitUserName !== issue.ownerName")
62 |
63 | el-dropdown(size="small" split-button type="primary" @click="startWork") Start work
64 | el-dropdown-menu(slot='dropdown')
65 | el-dropdown-item(@click.native='openLink(issueUrl)'
66 | v-if='issueUrl && issue.status !== "Closed"') View issue
67 | el-dropdown-item(@click.native='openLink(contractUrl)'
68 | v-if='contractUrl && issue.status !== "Closed"') View bounty contract
69 |
70 | div.card-action-footer.job-status(v-if='user.gitUserName === issue.ownerName && !isFunding') You added this issue
71 | div.card-action-footer.job-status(v-if='isForking') Currently forking
72 |
73 |
74 | div.card-action-footer(v-if="issue.contractAddress && issue.status !== 'Closed' && userJob && userJob.branchUrl && !userJob.pullRequestId")
75 | el-dropdown(placement="bottom" size="small" split-button type="primary" @click="submitWork") Submit as pull request
76 | el-dropdown-menu(slot='dropdown')
77 | el-dropdown-item(@click.native='cancelWork()'
78 | v-if='contractUrl && issue.status !== "Closed"') Cancel contribution
79 | el-dropdown-item(divided @click.native='showGitCommand()'
80 | v-if='issue.status !== "Closed"') View git commands
81 | el-dropdown-item(@click.native='openJobBranch(userJob.forkName, userJob.branchUrl)'
82 | v-if='userJob && !userJob.pullRequestNumber && userJob.forkName && userJob.branchUrl && issue.status !== "Closed"') View work branch
83 | el-dropdown-item(@click.native='openLink(issueUrl)'
84 | v-if='issueUrl && issue.status !== "Closed"') View issue
85 | el-dropdown-item(@click.native='openLink(contractUrl)'
86 | v-if='contractUrl && issue.status !== "Closed"') View bounty contract
87 |
88 |
89 | div.card-action-footer(v-if="issue.status !== 'Closed' && userJob && userJob.pullRequestId && user.gitUserName !== issue.ownerName")
90 | el-dropdown(placement="bottom" size="small" split-button type="primary" @click="openPullRequest(userJob.pullRequestNumber)") View Pull Request
91 | el-dropdown-menu(slot='dropdown')
92 | el-dropdown-item(@click.native='cancelWork()'
93 | v-if='contractUrl && issue.status !== "Closed"') Cancel contribution
94 | el-dropdown-item(divided @click.native='openLink(issueUrl)'
95 | v-if='issueUrl && issue.status !== "Closed"') View issue
96 | el-dropdown-item(@click.native='openLink(contractUrl)'
97 | v-if='contractUrl && issue.status !== "Closed"') View bounty contract
98 |
99 | div(v-if='issue.status != "Closed" && issue.contractAddress')
100 | p.tiny-text.dev-count(type='info',v-if='sentJobs && sentJobs.length > 1') {{sentJobs.length}} developers have started work
101 | p.tiny-text.dev-count(type='info',v-if='sentJobs && sentJobs.length === 1') {{sentJobs.length}} developer has started work
102 | p.tiny-text.dev-count(type='info',v-if='sentJobs && sentJobs.length === 0') no developers have started work
103 |
104 |
105 |
106 |
338 |
--------------------------------------------------------------------------------
/src/components/core/JobLink.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div.card-text.text-user(v-if="status == 'forking'")
4 | icon(icon-name="repo-forked")
5 | a(:href="jobPullRequestUrl" target="_blank") {{job.developerName}}
6 | el-tooltip(effect="dark" :content="`The job is being started by ${job.developerName}`" placement="top-end")
7 | div.float-right.job-status
8 | icon(icon-name="clock")
9 | span Forking
10 |
11 | div.card-text.text-repo(v-if="status == 'pending'")
12 | icon(icon-name="git-branch")
13 | a(:href="jobBranchUrl" target="_blank") {{job.forkName}}
14 | el-tooltip(effect="dark" :content="`${job.developerName} is currently working on this, once done, a pull request will be sent`" placement="top-end")
15 | div.float-right.job-status
16 | icon(icon-name="clock")
17 | span In progress
18 |
19 | div.card-text.text-user(v-if="status == 'ready'")
20 | icon(icon-name="git-pull-request")
21 | a(:href="jobPullRequestUrl" target="_blank") {{job.forkName}}
22 | el-tooltip(effect="dark" :content="`The job has been completed by ${job.developerName} and is ready for review`" placement="top-end")
23 | div.float-right.job-status
24 | icon(icon-name="check")
25 | span Submitted
26 |
27 |
28 |
29 |
56 |
57 |
63 |
--------------------------------------------------------------------------------
/src/components/core/RepoBox.vue:
--------------------------------------------------------------------------------
1 |
2 | el-card.box-card.card-small.d-flex.flex-column.justify-content-between(:body-style="{ padding: '0px' }" :class="isOwner && canEnable ? 'card-info' : 'card-primary'")
3 | br
4 | div.card-section
5 | span.card-text.text-repo-big
6 | icon(icon-name="repo" v-if='!repo.private')
7 | icon(icon-name="lock" v-if='repo.private')
8 | a(:href="repoUrl(repo.name)" target="_blank") {{repo.name.split('/')[1]}}
9 |
10 | div.card-text.text-user
11 | icon(icon-name="person" v-if='!repo.private')
12 | icon(icon-name="organization" v-if='repo.private')
13 | a(:href="repoUrl(repo.name)" target="_blank") {{repo.name.split('/')[0]}}
14 |
15 | div.card-section
16 | br
17 | br
18 | div.card-section
19 | br
20 |
21 | div.card-action-footer(v-if="isOwner && canEnable")
22 | el-button(size="small" type="primary" @click="enable") Enable repository
23 |
24 | div.card-action-footer(v-if="isOwner && !canEnable && metamask")
25 | el-dropdown(size="small" split-button type="primary" @click="createIssue") Create issue
26 | el-dropdown-menu(slot='dropdown')
27 | el-dropdown-item(@click.native='sponsorIssue' ) Bounty issue
28 | el-dropdown-item(@click.native='disable' ) Disable repository
29 |
30 | div.card-action-footer(v-if='metamask && !isOwner')
31 | el-button(size="small" type="primary" @click="sponsorIssue") Sponsor issue
32 |
33 | el-tooltip(v-if="!metamask && !canEnable" effect="dark" content="Please install MetaMask to be able to bounty issues." placement="top-start")
34 | div.card-action-footer(v-if="!metamask")
35 | el-button(disabled size="small" type='primary') Bounty issue
36 |
37 |
38 |
39 |
73 |
--------------------------------------------------------------------------------
/src/components/core/SectionBar.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | el-row
4 | el-col(:span='18')
5 | div.section-heading
6 | a(:href="'#'+anchor") {{title}}
7 | div.section-info {{subtitle}}
8 | el-col(:span='4' v-if="value!==undefined")
9 | el-input(type='text' v-bind:value="value" v-on:input="search($event)" placeholder='Search' )
10 | el-col.float-right(:span='2' v-if="withFilter")
11 | el-button.float-right(v-if="withFilter" type="primary" @click="filterToggled=!filterToggled")
12 | icon(icon-name="settings" :rotate="0.25")
13 | hr
14 | slide-up-down(:active="filterToggled" :duration="500")
15 | el-form(label-position="top" label-width="100px")
16 | el-row
17 | slot
18 | hr
19 |
20 |
21 |
22 |
44 |
45 |
48 |
--------------------------------------------------------------------------------
/src/components/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 | nav.gm-footer(v-if='$parent.showWelcome === false')
3 | .gm-footer__content
4 | .triple-item
5 | router-link.triple-item__item(id='about', :to=`{ name: 'about' }`) About GitMan
6 |
7 |
--------------------------------------------------------------------------------
/src/components/layout/Header.vue:
--------------------------------------------------------------------------------
1 |
2 | nav.gm-header
3 | .gm-header__logo
4 | .gm-header__content
5 | .triple-item
6 | router-link.triple-item__item(:v-if="userJobs && userJobs.length > 0 && userCreatedIssues && userCreatedIssues.length > 0" :to=`{ name: 'dashboard' }`) My Jobs
7 | router-link.triple-item__item(:to=`{ name: 'issues' }`) Eliminate Issue
8 | router-link.triple-item__item(:to=`{ name: 'repos' }`) Bounty Issue
9 | .gm-header__user
10 | el-dropdown(trigger='click' size="small")
11 | .circle-item
12 | .circle-item__circle(:style='{ backgroundImage: `url(${user.photoUrl})`}')
13 | .circle-item__text {{user.name}}
14 | el-dropdown-menu(slot='dropdown')
15 |
16 | el-dropdown-item(v-if="user && user.isAdmin")
17 | el-button(type='text' size="small" @click='sponsorRepo()') Sponsor repo
18 |
19 | el-dropdown-item
20 | el-button(type='text' size="small" @click='openTelegram()') Chat to us
21 | img.telegram(src='https://telegram.org/img/t_logo.png')
22 |
23 | el-dropdown-item
24 | el-button(type='text' size="small" @click='openVideo()') Help video
25 | img.youtube(src='https://www.youtube.com/yt/about/media/images/brand-resources/icons/YouTube_icon_full-color.svg')
26 |
27 | el-dropdown-item(:divided="true")
28 | el-button(type='text', size="small" @click='logout()') Sign out
29 |
30 |
31 |
32 |
64 |
--------------------------------------------------------------------------------
/src/components/layout/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 | div.v-sidebar-menu(:class="[!isCollapsed ? 'vsm-default' : 'vsm-collapsed', theme, rtl ? 'rtl' : '']" :style="{'width': sidebarWidth}" @mouseleave="mouseLeave")
3 |
4 | div.vsm-list
5 | div(v-if="!isCollapsed")
6 |
7 | slot
8 |
9 | el-button(type="primary" circle @click="toggleCollapse" class="help-button" style="text-align")
10 | icon.help-icon(icon-name="question" :height="32" :width="32")
11 |
12 |
13 |
14 |
128 |
129 |
132 |
133 |
150 |
--------------------------------------------------------------------------------
/src/components/pages/Contact.vue:
--------------------------------------------------------------------------------
1 |
2 | section(class='body-section', v-loading.fullscreen.lock='loading')
3 | AppHeader
4 |
5 | AppFooter
6 |
7 |
8 |
42 |
--------------------------------------------------------------------------------
/src/components/pages/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | section.body-section(v-loading.fullscreen.lock='loading' :element-loading-text="loadingText")
4 | AppHeader
5 |
6 | section.container.section
7 |
8 | section-bar(title="Your contributions" subtitle="Bounties your are currently working on" :with-filter="true")
9 | el-col(:span="6")
10 | el-form-item(label="Issue progress" prop="name")
11 | el-switch(v-model="workingFilter.isActive" active-text="Active" inactive-text="Completed" :active-value="true" :inactive-value="false")
12 | el-col(:span="6")
13 | el-form-item(label="Ethereum network" prop="name")
14 | el-switch.filter-switch(v-model="workingFilter.network" active-text="Mainnet" inactive-text="Testnet" active-value="main" inactive-value="rinkeby")
15 |
16 | el-row(:gutter='20' )
17 | el-col(v-for='issue in contributingIssues',:span=12, :key="issue.id")
18 | issue-box(
19 | v-if='user'
20 | :user="user"
21 | :issue="issue"
22 | :eth-usd="ethUSD"
23 | :jobs="jobs"
24 | v-on:git-command="openGitCommand(issue, $event)"
25 | )
26 |
27 | section-bar(v-if="userCreatedIssues && userCreatedIssues.length > 0" title="Your posted bounties" subtitle="Issues created or sponsored by you" :with-filter="true")
28 | el-col(:span="6")
29 | el-form-item(label="Issue progress" prop="name")
30 | el-switch.filter-switch(v-model="createdFilter.isActive" active-text="Active" inactive-text="Completed" :active-value="true" :inactive-value="false")
31 | el-col(:span="6")
32 | el-form-item(label="Ethereum network" prop="name")
33 | el-switch.filter-switch(v-model="createdFilter.network" active-text="Mainnet" inactive-text="Testnet" active-value="main" inactive-value="rinkeby")
34 |
35 | el-row(v-if="userCreatedIssues && userCreatedIssues.length > 0" :gutter='20')
36 | el-col(v-for='issue in createdIssues' :span=12, :key="issue.id")
37 | issue-box(
38 | :user="user"
39 | :issue="issue"
40 | :jobs="jobs"
41 | :eth-usd="ethUSD"
42 | v-on:git-command="openGitCommand(issue, $event)"
43 | )
44 | AppFooter
45 |
46 |
47 |
157 |
--------------------------------------------------------------------------------
/src/components/pages/ExternalLogin.vue:
--------------------------------------------------------------------------------
1 |
2 | section.login-container(v-loading.fullscreen.lock='loading')
3 |
4 | div
5 | .login-container__content
6 | .logo-large
7 |
8 | a.text-center Application Key
9 | el-input(type='text' name='appKey', v-model='appKey', placeholder='Application Key')
10 |
11 | a.text-center Token
12 | el-input(type='text' name='Token' v-model='token' placeholder='Paste token here')
13 |
14 | a(:href="githubLoginUrl")
15 |
16 | el-row(type="flex" justify="center")
17 | el-tag.error-login(v-if="errorMessage" closable type="danger") {{errorMessage}}
18 |
19 | el-row(type="flex" justify="center")
20 | .button-login(type='button', @click='login') Login
21 |
22 |
23 |
24 |
75 |
--------------------------------------------------------------------------------
/src/components/pages/Issues.vue:
--------------------------------------------------------------------------------
1 |
2 | section(class='body-section' v-loading.fullscreen.lock='loading || forkingJob' :element-loading-text="forkingJob ? `Forking the repository ${forkingJob.repositoryName} this can take a minute or two` : loadingText")
3 | AppHeader
4 |
5 | div.transition(name='modal')
6 | div(class='modal-mask', v-if='showGitCommandModal')
7 | div(class='modal-wrapper')
8 | div(class='modal-container-large gitCommands')
9 | div(class='modal-header')
10 | slot(name='header')
11 | h1 Starting Work
12 | div(class='modal-body bottom-pad-medium')
13 | .slot(name='body')
14 | div.bottom-pad-medium Use the git commands below to get started.
15 | div.bottom-pad-medium If you need to clone the repository use the command below
16 | pre
17 | code {{gitCommandTextClone}}
18 | br
19 | br
20 | div.bottom-pad-medium If you have already cloned the repository use the command below
21 | pre
22 | code {{gitCommandTextGetBranch}}
23 | br
24 | br
25 | div.bottom-pad-medium When your issue is fixed, just click on submit as pull request , this will send the pull request on github for approval by the repository owner
26 | br
27 | br
28 | el-button(class="float-right" type='secondary', @click='showGitCommandModal = false') Close
29 |
30 | section.container.section
31 | sidebar
32 | help-developer
33 |
34 | section-bar(title="Available bounties" subtitle="Bounties currenlty open to be worked on" :with-filter="true" v-model="search")
35 | el-form-item(label="Ethereum network" prop="name")
36 | el-switch.filter-switch(v-model="networkFilter" active-text="Mainnet" inactive-text="Testnet" active-value="main" inactive-value="rinkeby")
37 | el-row(:gutter='20')
38 | el-col(v-for='issue in availableIssues',:span=12, :key="issue.id")
39 | issue-box(v-if='canViewIssue(issue) && user'
40 | :user="user"
41 | :issue="issue"
42 | :eth-usd="ethUSD"
43 | :jobs="jobs"
44 | v-on:git-command="openGitCommand(issue, $event)"
45 | )
46 | AppFooter
47 |
48 |
49 |
124 |
--------------------------------------------------------------------------------
/src/components/pages/Login.vue:
--------------------------------------------------------------------------------
1 |
2 | section.login-container(v-loading.fullscreen.lock='loading')
3 |
4 | div
5 | .login-container__content
6 | .logo-large
7 |
8 | h1.text-center Beta
9 |
10 | el-row(type="flex" justify="center")
11 | el-tag.error-login(v-if="errorMessage" closable type="danger") {{errorMessage}}
12 |
13 | el-row(type="flex" justify="center")
14 | .button-login(type='button', @click='login') Continue with GitHub
15 |
16 |
17 |
18 |
48 |
--------------------------------------------------------------------------------
/src/components/pages/Repos.vue:
--------------------------------------------------------------------------------
1 |
2 | section.body-section(v-loading.fullscreen.lock='loading' :element-loading-text="loadingText")
3 | AppHeader
4 |
5 |
6 | transition(name='modal')
7 | div(class='modal-mask', v-if='showModal')
8 | div(class='modal-wrapper')
9 | div.modal-container-small
10 | div(class='modal-header')
11 | slot(name='header')
12 | div(v-if="formMode !== 'sponsor'" class='modal-heading') Bounty an issue
13 | div(v-if="formMode == 'sponsor'" class='modal-heading') Bounty an existing issue
14 | div(v-if="formMode !== 'sponsor'" class='modal-description') By raising a bounty, GitMan creates an issue against your selected repository.
15 | div(v-if="formMode === 'sponsor'" class='modal-description') By funding an existing issue, GitMan adds a bounty to the selected issue.
16 |
17 | div(v-if="network" class='modal-description') The bounty for this issue will be set on the Ethereum
18 | div(v-if="network" class='modal-description')
19 | el-tag.el-tag-net(size="small") {{network !== 'main' ? network : 'main'}} network
20 | div(v-if="network" class='modal-description') You can change the network via metamask if you wish
21 |
22 | div(class='modal-body bottom-pad-medium')
23 | slot(name='body')
24 | div(v-if="formMode === 'create'").bottom-pad-medium
25 | span.requiredmessage(v-if="!$v.issue.name.required") *
26 | el-input(type='text', name='name', v-model='issue.name', placeholder='Title', @input="$v.issue.name.$touch()")
27 | div(v-if="formMode === 'create'").bottom-pad-medium
28 | span.requiredmessage(v-if="!$v.issue.description.required") *
29 | el-input(type='textarea', name='description', v-model='issue.description', placeholder='Description', @input="$v.issue.description.$touch()")
30 |
31 | div(v-if="formMode === 'sponsor'")
32 | span.requiredmessage(v-if="repoIssues && repoIssues.length === 0") Sorry no issues available to sponsor or fund
33 | span.requiredmessage(v-if='!$v.issue.number.required') *
34 | el-select(:disabled='!repoIssues || repoIssues.length === 0', v-model='issue.number', :placeholder="!repoIssues || repoIssues.length == 0 ? 'Issues, loading ...' : 'Issue'", name='issueNumber' class='bottom-pad-medium full-width')
35 | el-option(
36 | v-for='item in repoIssues'
37 | :key='item.value'
38 | :label='item.label'
39 | :value='item.value')
40 |
41 | span.requiredmessage(v-if='!$v.issue.branch.required') *
42 | el-select(:disabled='!branches || branches.length === 0', v-model='issue.branch', :placeholder="!branches || branches.length == 0 ? 'Branches, loading ...' : 'Branch'", name='issueBranch' class='bottom-pad-medium full-width')
43 | el-option(
44 | v-for='item in branches'
45 | :key='item.value'
46 | :label='item.label'
47 | :value='item.value')
48 | span.requiredmessage(v-if='!$v.issue.bounty.required') *
49 | el-select(v-model='issue.bounty', placeholder='Bounty', name='issueBounty', class="full-width")
50 | el-option(
51 | v-for='item in formatedRewardAmounts'
52 | :key='item.value'
53 | :label='item.label'
54 | :value='item.value')
55 | div.tiny-text
56 | span(v-if="issue.bounty") fee: {{formatAmount(issue.bounty * 0.05)}}
57 | br
58 | span(v-if="issue.bounty") reward: {{formatAmount(issue.bounty * 0.85)}}
59 |
60 | div(class='modal-footer')
61 | slot(name='footer')
62 | el-button(
63 | v-if="formMode !== 'sponsor'"
64 | type='primary',
65 | :disabled='$v.$invalid'
66 | @click='addIssue()') Bounty Issue
67 |
68 | el-button(
69 | v-if="formMode === 'sponsor'"
70 | type='primary',
71 | :disabled='$v.$invalid'
72 | @click='addIssue()') Fund Issue
73 |
74 | el-button(
75 | type='secondary',
76 | @click='closeModal()',
77 | class="float-right") Close
78 |
79 |
80 |
81 | section.container.section
82 | sidebar
83 | help-donator
84 |
85 | section-bar(v-if="registeredOwnedRepos && registeredOwnedRepos.length > 0" title="Your registered repositories" subtitle="Repositories enabled for you or others to post bounties on them")
86 |
87 | el-row(:gutter=20 v-if='registeredOwnedRepos && registeredOwnedRepos.length > 0')
88 | el-col(v-for='repo in registeredOwnedRepos' :key="repo.name" :span="8")
89 | repo-box(
90 | :repo="repo"
91 | :can-create="repo.hasHook"
92 | :can-sponsor="!repo.private && repo.hasHook"
93 | :can-enable="!repo.hasHook"
94 | :metamask="metamaskAvailable"
95 | :isOwner="true"
96 | v-on:enable="enableRepository(repo.name)"
97 | v-on:disable="disableRepository(repo.name)"
98 | v-on:create-issue="openModal('create', repo.name)"
99 | v-on:sponsor-issue="openModal('sponsor', repo.name)")
100 |
101 | section-bar(ref="disabled-repos" id="disabled-repos" v-if="disabledOwnedRepos && disabledOwnedRepos.length > 0" anchor="disabled-repos" title="Your available repositories" subtitle="Repositories available for you to enable, so that you or others can post bounties on them")
102 |
103 | el-row(:gutter=20 v-if='disabledOwnedRepos && disabledOwnedRepos.length > 0')
104 | el-col(v-for='repo in disabledOwnedRepos' :key="repo.name" :span="8")
105 | repo-box(:id="'repo-'+repo.id"
106 | :repo="repo"
107 | :can-create="repo.hasHook"
108 | :can-sponsor="!repo.private && repo.hasHook"
109 | :can-enable="!repo.hasHook"
110 | :metamask="metamaskAvailable"
111 | :isOwner="true"
112 | v-on:enable="enableRepository(repo.name)"
113 | v-on:disable="disableRepository(repo.name)"
114 | v-on:create-issue="openModal('create', repo.name)"
115 | v-on:sponsor-issue="openModal('sponsor', repo.name)")
116 |
117 | section-bar(title="Repositories available for sponsoring" subtitle="Enabled repositories available for you to sponsor issues on them", v-model="searchSponsorRepo")
118 |
119 | el-row(:gutter=20)
120 | el-col(v-for='repo in sponsoringRepos' :key="repo.name" :span="8")
121 | repo-box(v-if="repo.canCreateIssue || !repo.private" :repo="repo" :can-enable="false" :can-create="repo.canCreateIssue" :can-sponsor="!repo.private" :metamask="metamaskAvailable" :isOwner="false" v-on:create-issue="openModal('create', repo.name)" v-on:sponsor-issue="openModal('sponsor', repo.name)")
122 |
123 | AppFooter
124 |
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 |
--------------------------------------------------------------------------------