├── src ├── containers │ ├── Login │ │ ├── constants.js │ │ ├── Login.module.scss │ │ ├── LoginLock.module.scss │ │ ├── selectors.js │ │ ├── components │ │ │ ├── baseStyles.module.scss │ │ │ ├── index.js │ │ │ ├── SigningIn.js │ │ │ ├── ErrorModal.js │ │ │ └── SignIn.js │ │ └── sagas.js │ ├── Leaderboard │ │ ├── selectors.js │ │ ├── components │ │ │ ├── index.js │ │ │ ├── LeaderItem.module.scss │ │ │ └── LeaderItem.js │ │ ├── Banner.module.scss │ │ ├── Leaderboard.module.scss │ │ ├── index.js │ │ ├── reducer.js │ │ └── LeaderboardCard.module.scss │ ├── Settings │ │ ├── components │ │ │ ├── index.js │ │ │ ├── PreferencesToggle.module.scss │ │ │ └── PreferencesToggle.js │ │ ├── selectors.js │ │ ├── Settings.module.scss │ │ ├── constants.js │ │ ├── reducer.js │ │ └── index.js │ ├── SubmissionsPanel │ │ ├── constants.js │ │ ├── selectors.js │ │ ├── reducer.js │ │ ├── SubmissionsPanel.module.scss │ │ └── sagas.js │ ├── Profile │ │ ├── components │ │ │ ├── NetworkStats │ │ │ │ ├── constants.js │ │ │ │ └── NetworkStats.module.scss │ │ │ ├── BaseStyles.module.scss │ │ │ ├── ProfileAvatar │ │ │ │ ├── ProfileAvatar.module.scss │ │ │ │ └── index.js │ │ │ ├── Skills │ │ │ │ ├── Skills.module.scss │ │ │ │ └── index.js │ │ │ ├── ProfileTabs │ │ │ │ ├── ProfileTabs.module.scss │ │ │ │ └── index.js │ │ │ ├── ReviewsModal │ │ │ │ ├── ReviewModal.module.scss │ │ │ │ └── ReviewItem.module.scss │ │ │ ├── index.js │ │ │ ├── Elsewhere │ │ │ │ └── Elsewhere.module.scss │ │ │ ├── About │ │ │ │ └── About.module.scss │ │ │ └── SEOHeader.js │ │ ├── constants.js │ │ ├── selectors.js │ │ ├── Profile.module.scss │ │ └── sagas.js │ ├── FilterNav │ │ └── index.js │ ├── Dashboard │ │ └── reducer.js │ ├── Bounty │ │ ├── ActionBar.module.scss │ │ ├── components │ │ │ ├── modals │ │ │ │ ├── IssueRatingFormModal │ │ │ │ │ └── IssueRatingFormModal.module.scss │ │ │ │ ├── Modals.module.scss │ │ │ │ ├── ExtendDeadlineErrorModal.js │ │ │ │ └── KillBountyFormModal.js │ │ │ ├── listItems │ │ │ │ ├── CommentItem.module.scss │ │ │ │ ├── ApplicantItem.module.scss │ │ │ │ └── CommentItem.js │ │ │ ├── NewCommentForm.module.scss │ │ │ └── cards │ │ │ │ └── Cards.module.scss │ │ ├── selectors.js │ │ └── BountyPageCards.module.scss │ ├── CreateBounty │ │ ├── sagas.js │ │ ├── selectors.js │ │ └── handle-choose-template.js │ ├── Explorer │ │ └── Explorer.module.scss │ ├── BountiesPanel │ │ ├── reducer.js │ │ ├── BountiesPanel.module.scss │ │ └── selectors.js │ └── ActivityPanel │ │ └── ActivityPanel.module.scss ├── layout │ ├── TOS │ │ └── TOS.module.scss │ ├── Privacy │ │ └── Privacy.module.scss │ ├── NoMatch │ │ └── index.js │ └── App │ │ ├── App.test.js │ │ ├── selectors.js │ │ └── constants.js ├── public-modules │ ├── Skills │ │ ├── constants.js │ │ ├── selectors.js │ │ └── sagas.js │ ├── Tokens │ │ ├── constants.js │ │ ├── selectors.js │ │ ├── sagas.js │ │ └── index.js │ ├── Categories │ │ ├── constants.js │ │ ├── selectors.js │ │ └── sagas.js │ ├── Fulfillment │ │ ├── constants.js │ │ └── selectors.js │ ├── Languages │ │ ├── constants.js │ │ ├── selectors.js │ │ └── sagas.js │ ├── Settings │ │ ├── constants.js │ │ └── selectors.js │ ├── Transaction │ │ ├── constants.js │ │ └── selectors.js │ ├── UserInfo │ │ ├── constants.js │ │ └── sagas.js │ ├── Authentication │ │ ├── constants.js │ │ └── selectors.js │ ├── Drafts │ │ ├── constants.js │ │ └── selectors.js │ ├── Review │ │ ├── constants.js │ │ ├── selectors.js │ │ └── sagas.js │ ├── Reviews │ │ ├── constants.js │ │ └── selectors.js │ ├── Activity │ │ ├── constants.js │ │ └── selectors.js │ ├── Applicants │ │ ├── constants.js │ │ └── selectors.js │ ├── Fulfillments │ │ ├── constants.js │ │ └── selectors.js │ ├── Leaderboard │ │ ├── constants.js │ │ └── selectors.js │ ├── Notification │ │ ├── constants.js │ │ ├── selectors.js │ │ ├── helpers.ts │ │ └── __generated__ │ │ │ └── userDashboardNotifications.ts │ ├── Comments │ │ ├── constants.js │ │ └── selectors.js │ ├── FulfillerApplication │ │ ├── selectors.js │ │ └── sagas.js │ ├── FileUpload │ │ ├── constants.js │ │ ├── selectors.js │ │ └── sagas.js │ ├── i18n │ │ ├── constants.js │ │ ├── selectors.js │ │ └── index.js │ ├── Utilities │ │ ├── Web3Client.js │ │ └── ipfsClient.js │ ├── ipfs.ts │ ├── Bounty │ │ ├── constants.js │ │ └── selectors.js │ ├── config.js │ ├── Bounties │ │ └── constants.js │ ├── Client │ │ └── selectors.js │ ├── hiring_settings.json │ ├── sa_settings.json │ ├── sf_settings.json │ ├── bgc_settings.json │ ├── nyc_settings.json │ ├── japan_settings.json │ ├── korea_settings.json │ ├── paris_settings.json │ ├── prague_settings.json │ ├── colorado_settings.json │ ├── coverage_settings.json │ ├── local_settings.json │ ├── ethscholars_settings.json │ ├── surge_settings.json │ ├── berlin_settings.json │ ├── boston_settings.json │ ├── moksha_settings.json │ ├── test_coverage_settings.json │ ├── waterloo_settings.json │ ├── staging_settings.json │ └── rinkeby_settings.json ├── components │ ├── Toggle │ │ ├── Toggle.module.scss │ │ └── index.js │ ├── DatePicker │ │ └── DatePicker.module.scss │ ├── Currency │ │ └── Currency.module.scss │ ├── Network │ │ ├── Network.module.scss │ │ └── index.js │ ├── Social │ │ ├── Social.module.scss │ │ └── index.js │ ├── Select │ │ └── Select.module.scss │ ├── Sort │ │ └── Sort.module.scss │ ├── ZeroState │ │ └── ZeroState.module.scss │ ├── Loading │ │ ├── index.js │ │ └── Loading.module.scss │ ├── SearchSelect │ │ └── SearchSelect.module.scss │ ├── Loader │ │ ├── index.js │ │ └── Loader.module.scss │ ├── Card │ │ └── Card.module.scss │ ├── MarkdownEditor │ │ └── MarkdownEditor.module.scss │ ├── Search │ │ ├── Search.module.scss │ │ └── index.js │ ├── Tabs │ │ └── Tabs.module.scss │ ├── PageBanner │ │ └── PageBanner.module.scss │ ├── Cropper │ │ └── Cropper.module.scss │ ├── ToastContainer │ │ ├── ToastContainer.module.scss │ │ └── index.js │ ├── CardNotification │ │ ├── CardNotification.module.scss │ │ └── index.js │ ├── ListGroup │ │ └── ListGroup.module.scss │ ├── Rating │ │ └── Rating.module.scss │ ├── TextInput │ │ └── TextInput.module.scss │ ├── Circle │ │ └── Circle.module.scss │ ├── Dropdown │ │ └── Dropdown.module.scss │ ├── Toast │ │ └── index.js │ ├── ProgressBar │ │ └── index.js │ ├── FileUpload │ │ └── FileUpload.module.scss │ ├── FullAddressBar │ │ ├── FullAddressBar.module.scss │ │ └── index.js │ └── Pill │ │ └── Pill.module.scss ├── lib │ ├── emotion-styled.ts │ └── rollbar.js ├── explorer-components │ ├── LinkedAvatar │ │ └── index.js │ ├── TransactionWalkthrough │ │ └── TransactionWalkthrough.module.scss │ ├── Currency │ │ └── index.js │ ├── BountyItem │ │ └── BountyItem.module.scss │ ├── FormSection │ │ └── FormSection.module.scss │ ├── SubmissionItem │ │ └── SubmissionItem.module.scss │ ├── NotificationItem │ │ └── NotificationItem.module.scss │ ├── StagePill │ │ └── index.js │ ├── PageCard │ │ └── PageCard.module.scss │ ├── ApplicantStagePill │ │ └── index.js │ ├── FilterNav │ │ └── FilterNav.module.scss │ └── FulfillmentStagePill │ │ └── index.js ├── utils │ ├── i18nHelpers.js │ ├── normalizers.js │ └── global.js ├── form-components │ ├── formik.js │ └── index.js ├── styles │ ├── NumberInput.scss │ ├── storybook.scss │ └── reset.scss ├── hocs │ ├── FetchComponent.js │ ├── LinkedComponent.js │ ├── FilterNavManager.js │ ├── ModalFormReset.js │ ├── LoadComponent.js │ ├── RequireLoginComponent.module.scss │ ├── CurrencyModifier.js │ └── FormInput.js ├── sagas.js ├── reducers.js └── stories │ ├── Loading.stories.js │ ├── Cropper.stories.js │ ├── Textbox.stories.js │ ├── Social.stories.js │ └── FullAddressBar.stories.js ├── public ├── favicon.ico └── manifest.json ├── .npmrc ├── .storybook ├── addons.js └── config.js ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js └── polyfills.js ├── .gitignore ├── __generated__ └── globalTypes.ts ├── apollo.config.js ├── scripts └── test.js ├── README.md ├── tsconfig.json ├── LICENSE └── babel.config.js /src/containers/Login/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/TOS/TOS.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/containers/Login/Login.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/Privacy/Privacy.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Skills/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Tokens/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Toggle/Toggle.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/containers/Login/LoginLock.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Categories/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Fulfillment/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Languages/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Settings/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Transaction/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/UserInfo/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/DatePicker/DatePicker.module.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Authentication/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public-modules/Drafts/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | -------------------------------------------------------------------------------- /src/public-modules/Review/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | -------------------------------------------------------------------------------- /src/public-modules/Reviews/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | -------------------------------------------------------------------------------- /src/public-modules/Activity/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 10; 2 | -------------------------------------------------------------------------------- /src/public-modules/Applicants/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 10; 2 | -------------------------------------------------------------------------------- /src/public-modules/Fulfillments/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 10; 2 | -------------------------------------------------------------------------------- /src/public-modules/Leaderboard/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | -------------------------------------------------------------------------------- /src/public-modules/Notification/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bounties-Network/Explorer/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/containers/Login/selectors.js: -------------------------------------------------------------------------------- 1 | export const rootLoginSelector = state => state.loginContainer; 2 | -------------------------------------------------------------------------------- /src/public-modules/Comments/constants.js: -------------------------------------------------------------------------------- 1 | export const LIMIT = 25; 2 | export const LIMIT_FUL = 5; 3 | -------------------------------------------------------------------------------- /src/public-modules/FulfillerApplication/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | -------------------------------------------------------------------------------- /src/public-modules/Activity/selectors.js: -------------------------------------------------------------------------------- 1 | export const rootActivitySelector = state => state.activity; 2 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/selectors.js: -------------------------------------------------------------------------------- 1 | export const rootLeaderboardUISelector = state => state.leaderboardUI; 2 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/components/index.js: -------------------------------------------------------------------------------- 1 | import LeaderItem from './LeaderItem'; 2 | 3 | export { LeaderItem }; 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @fortawesome:registry=https://npm.fontawesome.com/ 2 | //npm.fontawesome.com/:_authToken=87FD21E3-F8F5-4D76-8BB6-181DD8C56D97 -------------------------------------------------------------------------------- /src/containers/Settings/components/index.js: -------------------------------------------------------------------------------- 1 | import PreferencesToggle from './PreferencesToggle'; 2 | 3 | export { PreferencesToggle }; 4 | -------------------------------------------------------------------------------- /src/components/Currency/Currency.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .primary { 4 | margin-bottom: $base-spacing; 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/SubmissionsPanel/constants.js: -------------------------------------------------------------------------------- 1 | export const FULFILLER_KEY = 'dashboard_fulfiller'; 2 | export const ISSUER_KEY = 'dashboard_issuer'; 3 | -------------------------------------------------------------------------------- /src/public-modules/FileUpload/constants.js: -------------------------------------------------------------------------------- 1 | export const IPFS_OPTIONS = { 2 | host: 'ipfs.infura.io', 3 | port: 5001, 4 | protocol: 'https' 5 | }; 6 | -------------------------------------------------------------------------------- /src/lib/emotion-styled.ts: -------------------------------------------------------------------------------- 1 | import styled, { CreateStyled } from '@emotion/styled'; 2 | import theme from 'theme'; 3 | 4 | export default styled as CreateStyled; 5 | -------------------------------------------------------------------------------- /src/explorer-components/LinkedAvatar/index.js: -------------------------------------------------------------------------------- 1 | import { LinkedComponent } from 'hocs'; 2 | import Avatar from 'components/Avatar'; 3 | 4 | export default LinkedComponent(Avatar); 5 | -------------------------------------------------------------------------------- /src/explorer-components/TransactionWalkthrough/TransactionWalkthrough.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .textBreak { 4 | margin-bottom: $xl-spacing; 5 | } 6 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-storysource/register' 2 | import '@storybook/addon-knobs/register' 3 | import '@storybook/addon-actions/register' 4 | import '@storybook/addon-links/register' 5 | -------------------------------------------------------------------------------- /src/layout/NoMatch/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import intl from 'react-intl-universal'; 3 | 4 | const NoMatch = () =>
{intl.get('errors.404')}
; 5 | 6 | export default NoMatch; 7 | -------------------------------------------------------------------------------- /src/public-modules/i18n/constants.js: -------------------------------------------------------------------------------- 1 | export const INIT_TRANSLATIONS = 'INIT_TRANSLATIONS'; 2 | export const UPDATE_LOCALE = 'UPDATE_LOCALE'; 3 | export const TRANSLATION_LOADED = 'TRANSLATION_LOADED'; 4 | -------------------------------------------------------------------------------- /src/explorer-components/Currency/index.js: -------------------------------------------------------------------------------- 1 | import { CurrencyModifier } from 'hocs'; 2 | import { Currency } from 'components'; 3 | 4 | const SmartCurrency = CurrencyModifier(Currency); 5 | export default SmartCurrency; 6 | -------------------------------------------------------------------------------- /src/utils/i18nHelpers.js: -------------------------------------------------------------------------------- 1 | import intl from 'react-intl-universal'; 2 | 3 | export function translateOption(prefix, option) { 4 | return { 5 | ...option, 6 | label: intl.get(prefix + '.' + option.label.toLowerCase()) 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/form-components/formik.js: -------------------------------------------------------------------------------- 1 | import { FormInput } from 'hocs'; 2 | import { Textbox, TextInput } from 'components'; 3 | 4 | export const FormTextInput = FormInput(TextInput, 'formik'); 5 | export const FormTextbox = FormInput(Textbox, 'formik'); 6 | -------------------------------------------------------------------------------- /src/containers/Profile/components/NetworkStats/constants.js: -------------------------------------------------------------------------------- 1 | export const statsToShow = ['acceptance', 'rating', 'ratingGiven']; 2 | 3 | export const displayFormat = { 4 | acceptance: 'percent', 5 | rating: 'fraction', 6 | ratingGiven: 'fraction' 7 | }; 8 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/Banner.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .headerWrapper { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | } 8 | 9 | .titleText { 10 | margin-bottom: $l-spacing; 11 | } 12 | -------------------------------------------------------------------------------- /src/public-modules/Review/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootReviewSelector = state => state.review; 4 | 5 | export const reviewSelector = createSelector( 6 | rootReviewSelector, 7 | rootReview => rootReview 8 | ); 9 | -------------------------------------------------------------------------------- /src/public-modules/Utilities/Web3Client.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | 3 | const web3 = new Web3( 4 | new Web3.providers.HttpProvider( 5 | 'https://mainnet.infura.io/v3/5eb45628ce2c4ecebcce7f201f352792' 6 | ) 7 | ); 8 | 9 | export default web3; 10 | -------------------------------------------------------------------------------- /src/containers/FilterNav/index.js: -------------------------------------------------------------------------------- 1 | import { FilterNav as FilterNavHOC } from 'hocs'; 2 | import { FilterNav } from 'explorer-components'; 3 | 4 | const LinkedFilterNav = FilterNavHOC(FilterNav); 5 | 6 | export const BountyFilterNav = LinkedFilterNav({ type: 'bounty' }); 7 | -------------------------------------------------------------------------------- /src/public-modules/Tokens/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootTokensSelector = state => state.tokens; 4 | 5 | export const tokensSelector = createSelector( 6 | rootTokensSelector, 7 | rootTokens => rootTokens.tokens 8 | ); 9 | -------------------------------------------------------------------------------- /src/containers/Settings/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootSettingsUISelector = state => state.settingsUI; 4 | 5 | export const settingsUISelector = createSelector( 6 | rootSettingsUISelector, 7 | rootSettingsUI => rootSettingsUI 8 | ); 9 | -------------------------------------------------------------------------------- /src/public-modules/FileUpload/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootUploadSelector = state => state.fileUpload; 4 | 5 | export const getUploadKeySelector = key => { 6 | return createSelector(rootUploadSelector, upload => upload[key]); 7 | }; 8 | -------------------------------------------------------------------------------- /src/public-modules/i18n/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const translationSelector = state => state.i18n; 4 | 5 | export const currentLocaleSelector = createSelector( 6 | translationSelector, 7 | translation => translation.currentLocale 8 | ); 9 | -------------------------------------------------------------------------------- /src/public-modules/Applicants/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootApplicantsSelector = state => state.applicants; 4 | 5 | export const applicantsSelector = createSelector( 6 | rootApplicantsSelector, 7 | rootApplicants => rootApplicants 8 | ); 9 | -------------------------------------------------------------------------------- /src/public-modules/Fulfillment/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootFulfillmentSelector = state => state.fulfillment; 4 | 5 | export const fulfillmentSelector = createSelector( 6 | rootFulfillmentSelector, 7 | rootFulfillment => rootFulfillment 8 | ); 9 | -------------------------------------------------------------------------------- /src/containers/Login/components/baseStyles.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .avatarWrapper { 4 | margin: 0 auto; 5 | padding: $xl-spacing 0; 6 | } 7 | 8 | .avatar { 9 | } 10 | 11 | .alignLeft { 12 | text-align: left; 13 | } 14 | 15 | .modal { 16 | margin: 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/layout/App/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/containers/SubmissionsPanel/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootSubmissionsPanelSelector = state => state.submissionsPanel; 4 | 5 | export const submissionsPanelSelector = createSelector( 6 | rootSubmissionsPanelSelector, 7 | submissionsPanel => submissionsPanel 8 | ); 9 | -------------------------------------------------------------------------------- /src/containers/Dashboard/reducer.js: -------------------------------------------------------------------------------- 1 | import bountiesPanelReducer from 'containers/BountiesPanel/reducer'; 2 | import submissionsPanelReducer from 'containers/SubmissionsPanel/reducer'; 3 | 4 | const reducers = { 5 | bountiesPanel: bountiesPanelReducer, 6 | submissionsPanel: submissionsPanelReducer 7 | }; 8 | 9 | export default reducers; 10 | -------------------------------------------------------------------------------- /src/utils/normalizers.js: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | const number = (value, prevValue) => { 4 | if (!value) { 5 | return value; 6 | } 7 | 8 | if (value === '.') { 9 | return value; 10 | } 11 | return BigNumber(value).isNaN() ? prevValue : value; 12 | }; 13 | 14 | export default { 15 | number 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/Network/Network.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .mainnetCircle { 4 | margin-right: $base-spacing; 5 | color: $brand-green; 6 | } 7 | 8 | .unknownCircle { 9 | margin-right: $base-spacing; 10 | color: $brand-red; 11 | } 12 | 13 | .rinkebyCircle { 14 | margin-right: $base-spacing; 15 | color: #FBAA31; 16 | } 17 | -------------------------------------------------------------------------------- /src/containers/Profile/components/BaseStyles.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | @mixin statContainer() { 4 | background-color: $brand-white; 5 | box-shadow: $dark-box-shadow; 6 | border-radius: $base-border-radius; 7 | padding: $l-spacing; 8 | height: 100%; 9 | 10 | > p { 11 | margin-bottom: $xl-spacing; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ProfileAvatar/ProfileAvatar.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .centerAvatar { 4 | display: flex; 5 | flex-flow: column; 6 | justify-content: center; 7 | align-items: center; 8 | padding-top: $xxl-spacing; 9 | } 10 | 11 | .profileName { 12 | padding-top: $xl-spacing; 13 | padding-bottom: $m-spacing; 14 | } 15 | -------------------------------------------------------------------------------- /src/containers/Profile/components/Skills/Skills.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | @import "../BaseStyles.module.scss"; 3 | 4 | .skills { 5 | @include statContainer; 6 | } 7 | 8 | .skillsContainer { 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | flex: 1; 13 | } 14 | 15 | .skill { 16 | margin-bottom: $base-spacing; 17 | } 18 | -------------------------------------------------------------------------------- /src/explorer-components/BountyItem/BountyItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .container { 4 | display: flex; 5 | align-items: center; 6 | } 7 | 8 | .details { 9 | margin-top: $base-spacing * 1.5; 10 | } 11 | 12 | .value { 13 | margin-left: -1rem; 14 | text-align: right; 15 | } 16 | 17 | .eth { 18 | margin-top: $base-spacing * 1.5; 19 | } 20 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/Social/Social.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .social { 4 | a { 5 | cursor: pointer; 6 | color: $brand-grey; 7 | text-decoration: none; 8 | font-size: $h3-font-size; 9 | 10 | &:hover { 11 | color: $brand-blue; 12 | } 13 | 14 | &:not(:last-child) { 15 | margin-right: $m-spacing; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Select/Select.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .select { 4 | } 5 | 6 | .error { 7 | :global(.Select-control) { 8 | box-shadow: 0 0 0 2px $brand-red; 9 | border-color: transparent; 10 | } 11 | } 12 | 13 | .selectComponent { 14 | } 15 | 16 | .inputHelpText { 17 | padding-top: $base-spacing; 18 | line-height: $line-height-small; 19 | } 20 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bounties Explorer", 3 | "name": "Bounties Explorer", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/public-modules/Drafts/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootDraftsSelector = state => state.drafts; 4 | 5 | export const draftsSelector = createSelector( 6 | rootDraftsSelector, 7 | rootDrafts => rootDrafts.drafts 8 | ); 9 | 10 | export const draftsCountSelector = createSelector( 11 | rootDraftsSelector, 12 | rootDrafts => rootDrafts.count 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Sort/Sort.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .sort { 4 | display: inline-block; 5 | cursor: pointer; 6 | 7 | &:not(:last-child) { 8 | margin-right: $l-spacing; 9 | } 10 | } 11 | 12 | .chevron { 13 | color: $brand-blue; 14 | font-size: $body-small-font-size; 15 | margin-left: $base-spacing; 16 | } 17 | 18 | .sortText { 19 | display: inline-block; 20 | } 21 | -------------------------------------------------------------------------------- /src/public-modules/Reviews/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootReviewsSelector = state => state.reviews; 4 | 5 | export const reviewsStateSelector = createSelector( 6 | rootReviewsSelector, 7 | rootReviews => rootReviews 8 | ); 9 | 10 | export const reviewsSelector = createSelector( 11 | reviewsStateSelector, 12 | reviewsState => reviewsState.reviews 13 | ); 14 | -------------------------------------------------------------------------------- /src/public-modules/ipfs.ts: -------------------------------------------------------------------------------- 1 | const ipfsConfig = { 2 | protocol: 'https', 3 | port: 5001, 4 | hostName: 'ipfs.bounties-network-flow.com', 5 | apiViewPath: `/api/v0/cat?arg=`, 6 | host: 'ipfs.bounties-network-flow.com' 7 | }; 8 | 9 | const apiViewURL = `${ipfsConfig.protocol}://${ipfsConfig.hostName}:${ 10 | ipfsConfig.port 11 | }${ipfsConfig.apiViewPath}`; 12 | 13 | export default { ...ipfsConfig, apiViewURL }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | storybook-static 24 | .vscode/launch.json 25 | .idea 26 | -------------------------------------------------------------------------------- /src/layout/App/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootAppSelector = state => state.app; 4 | export const routerSelector = state => state.router; 5 | 6 | export const locationNonceSelector = createSelector( 7 | rootAppSelector, 8 | rootApp => rootApp.locationNonce 9 | ); 10 | 11 | export const lastLocationSelector = createSelector( 12 | rootAppSelector, 13 | rootApp => rootApp.lastLocation 14 | ); 15 | -------------------------------------------------------------------------------- /src/styles/NumberInput.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .react-numeric-input { 4 | height: $base-input-height; 5 | padding: 0; 6 | } 7 | 8 | input.numberInput { 9 | background: $base-input-background; 10 | border: $base-border; 11 | height: $base-input-height; 12 | } 13 | 14 | b { 15 | background-color: white !important; 16 | } 17 | 18 | i { 19 | background: rgb(72, 148, 255) !important; 20 | cursor: pointer; 21 | } 22 | -------------------------------------------------------------------------------- /src/public-modules/Skills/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { map } from 'lodash'; 3 | 4 | export const rootSkillsSelector = state => state.skills; 5 | 6 | export const skillsSelector = createSelector( 7 | rootSkillsSelector, 8 | skillsSelector => skillsSelector.skills 9 | ); 10 | 11 | export const skillsListSelector = createSelector(skillsSelector, skills => 12 | map(skill => skill.normalized_name, skills) 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/ZeroState/ZeroState.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .zeroState { 4 | text-align: center; 5 | } 6 | 7 | .icon { 8 | margin-bottom: $xl-spacing; 9 | } 10 | 11 | .iconStyles { 12 | } 13 | 14 | .title { 15 | margin-bottom: 15px; 16 | } 17 | 18 | .text { 19 | line-height: 1.5em; 20 | } 21 | 22 | .subText { 23 | line-height: 1.5em; 24 | margin-top: $m-spacing; 25 | } 26 | 27 | .action { 28 | margin-top: 40px; 29 | } 30 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ProfileTabs/ProfileTabs.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | @import "../BaseStyles.module.scss"; 3 | 4 | .skills { 5 | @include statContainer; 6 | } 7 | 8 | .skillsContainer { 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | flex: 1; 13 | } 14 | 15 | .skill { 16 | margin-bottom: $base-spacing; 17 | } 18 | 19 | .centerTabs { 20 | display: flex; 21 | justify-content: center; 22 | } 23 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ReviewsModal/ReviewModal.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .modalBody { 4 | margin-top: $xl-spacing; 5 | padding-bottom: 0 !important; 6 | max-height: 25rem; 7 | margin-left: -$m-spacing; 8 | margin-right: -$m-spacing; 9 | } 10 | 11 | .loadMoreButton { 12 | width: 100%; 13 | text-align: center; 14 | margin-top: $m-spacing; 15 | } 16 | 17 | .reviewsZeroState { 18 | padding: $xl-spacing; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Loading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Loading.module.scss'; 3 | 4 | const Loading = props => { 5 | const { className } = props; 6 | 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Loading; 17 | -------------------------------------------------------------------------------- /src/hocs/FetchComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { curry } from 'lodash'; 3 | 4 | function FetchComponent(fetch, WrappedComponent) { 5 | return class Fetch extends Component { 6 | componentDidMount() { 7 | if (process.env.SSR) { 8 | return fetch(); 9 | } 10 | } 11 | 12 | render() { 13 | return ; 14 | } 15 | }; 16 | } 17 | 18 | export default curry(FetchComponent); 19 | -------------------------------------------------------------------------------- /src/containers/Bounty/ActionBar.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .actionBar { 4 | margin-bottom: $xxl-spacing; 5 | } 6 | 7 | .activateButton { 8 | margin-bottom: $base-spacing; 9 | } 10 | 11 | .editBountyButton { 12 | margin-bottom: $l-spacing; 13 | } 14 | 15 | .killButton { 16 | margin-bottom: $base-spacing; 17 | } 18 | 19 | .buttonGroup { 20 | margin-top: $base-spacing; 21 | } 22 | 23 | .reactivateButton { 24 | margin-bottom: $m-spacing; 25 | } 26 | -------------------------------------------------------------------------------- /src/public-modules/Comments/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootCommentsSelector = state => state.comments; 4 | export const rootFulCommentsSelector = state => state.comments; 5 | 6 | export const commentsSelector = createSelector( 7 | rootCommentsSelector, 8 | rootComments => rootComments 9 | ); 10 | export const fulCommentsSelector = createSelector( 11 | rootFulCommentsSelector, 12 | rootFulComments => rootFulComments 13 | ); 14 | -------------------------------------------------------------------------------- /__generated__/globalTypes.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // This file was automatically generated and should not be edited. 4 | 5 | //============================================================== 6 | // START Enums and Input Objects 7 | //============================================================== 8 | 9 | //============================================================== 10 | // END Enums and Input Objects 11 | //============================================================== 12 | -------------------------------------------------------------------------------- /src/public-modules/Categories/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { map } from 'lodash'; 3 | 4 | export const rootCategoriesSelector = state => state.categories; 5 | 6 | export const categoriesSelector = createSelector( 7 | rootCategoriesSelector, 8 | rootCategories => rootCategories.categories 9 | ); 10 | 11 | export const categoriesListSelector = createSelector( 12 | categoriesSelector, 13 | categories => map(category => category.normalized_name, categories) 14 | ); 15 | -------------------------------------------------------------------------------- /src/public-modules/Languages/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { map } from 'lodash'; 3 | 4 | export const rootLanguagesSelector = state => state.languages; 5 | 6 | export const languagesSelector = createSelector( 7 | rootLanguagesSelector, 8 | languagesSelector => languagesSelector.languages 9 | ); 10 | 11 | export const languagesListSelector = createSelector( 12 | languagesSelector, 13 | languages => map(language => language.normalized_name, languages) 14 | ); 15 | -------------------------------------------------------------------------------- /src/utils/global.js: -------------------------------------------------------------------------------- 1 | import config from 'public-modules/config'; 2 | import { flatten, map } from 'lodash'; 3 | 4 | export let web3 = {}; 5 | 6 | let API_ENDPOINT = config.url.mainNet; 7 | //let API_ENDPOINT = 'http://localhost:8000'; 8 | // update this to be an env passthrough 9 | export const apiEndpoint = { 10 | set: endpoint => (API_ENDPOINT = endpoint), 11 | get: () => API_ENDPOINT 12 | }; 13 | 14 | export const expandPlatforms = platforms => 15 | flatten(map(platform => config.platforms[platform], platforms)).join(','); 16 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ReviewsModal/ReviewItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .container { 4 | display: flex; 5 | align-items: flex-start; 6 | padding-bottom: $base-spacing; 7 | } 8 | 9 | .rating { 10 | width: 2rem; 11 | margin-right: $l-spacing; 12 | } 13 | 14 | .review { 15 | flex-grow: 1; 16 | padding-right: $l-spacing; 17 | 18 | > p:not(:last-child) { 19 | margin-bottom: $m-spacing; 20 | } 21 | } 22 | 23 | .created { 24 | min-width: 4rem; 25 | text-align: right; 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/Profile/components/index.js: -------------------------------------------------------------------------------- 1 | import About from './About'; 2 | import Elsewhere from './Elsewhere'; 3 | import NetworkStats from './NetworkStats'; 4 | import Skills from './Skills'; 5 | import ProfileAvatar from './ProfileAvatar'; 6 | import ProfileTabs from './ProfileTabs'; 7 | import ReviewsModal from './ReviewsModal'; 8 | import SEOHeader from './SEOHeader'; 9 | 10 | export { 11 | About, 12 | Elsewhere, 13 | NetworkStats, 14 | ProfileAvatar, 15 | ProfileTabs, 16 | ReviewsModal, 17 | Skills, 18 | SEOHeader 19 | }; 20 | -------------------------------------------------------------------------------- /src/containers/Profile/constants.js: -------------------------------------------------------------------------------- 1 | export const filterConfig = { 2 | rootConfig: { 3 | sort: true, 4 | search: false, 5 | stage: true, 6 | difficulty: false, 7 | category: false, 8 | platform: false 9 | }, 10 | resetFilters: { 11 | address: false, 12 | search: true, 13 | stage: true, 14 | difficulty: true, 15 | category: true, 16 | platform: true 17 | }, 18 | defaultStageFilters: { 19 | active: false, 20 | completed: false, 21 | expired: false, 22 | dead: false 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/SearchSelect/SearchSelect.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .dropdownSearch { 4 | width: 100%; 5 | margin-bottom: -$base-spacing; 6 | } 7 | 8 | .pillBar { 9 | padding-top: $base-spacing; 10 | display: flex; 11 | flex-wrap: wrap; 12 | } 13 | 14 | .pill { 15 | padding-right: 5px; 16 | padding-top: 5px; 17 | } 18 | 19 | .error { 20 | :global(.Select-control) { 21 | border: 2px solid $brand-red; 22 | } 23 | } 24 | 25 | .inputHelpText { 26 | padding-top: $base-spacing; 27 | line-height: $line-height-small; 28 | } 29 | -------------------------------------------------------------------------------- /src/public-modules/Bounty/constants.js: -------------------------------------------------------------------------------- 1 | export const DIFFICULTY_VALUES = { 2 | Beginner: 0, 3 | Intermediate: 1, 4 | Expert: 2 5 | }; 6 | 7 | export const DIFFICULTY_MAPPINGS = { 8 | 0: 'Beginner', 9 | 1: 'Intermediate', 10 | 2: 'Expert' 11 | }; 12 | 13 | export const STAGE_VALUES = { 14 | 0: 'stages.draft', 15 | 1: 'stages.active', 16 | 2: 'stages.dead', 17 | 3: 'stages.completed', 18 | 4: 'stages.expired' 19 | }; 20 | 21 | export const DRAFT = 0; 22 | export const ACTIVE = 1; 23 | export const DEAD = 2; 24 | export const COMPLETED = 3; 25 | export const EXPIRED = 4; 26 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/modals/IssueRatingFormModal/IssueRatingFormModal.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .inputGroup:last-child { 4 | margin-top: $xl-spacing; 5 | } 6 | 7 | .rowText { 8 | display: flex; 9 | justify-content: space-between; 10 | } 11 | 12 | .review { 13 | text-align: left; 14 | } 15 | 16 | .avatar { 17 | margin: $xl-spacing 0; 18 | text-align: left; 19 | color: $brand-black; 20 | display: flex; 21 | justify-content: center; 22 | } 23 | 24 | .submitError { 25 | margin-top: $base-spacing; 26 | margin-bottom: -$s-spacing; 27 | } 28 | -------------------------------------------------------------------------------- /src/containers/CreateBounty/sagas.js: -------------------------------------------------------------------------------- 1 | import { push } from 'connected-react-router'; 2 | import { put, takeLatest } from 'redux-saga/effects'; 3 | import { actionTypes as bountyActionTypes } from 'public-modules/Bounty'; 4 | 5 | const { CREATE_DRAFT_SUCCESS } = bountyActionTypes; 6 | 7 | export function* bountyCreated(action) { 8 | const { bounty } = action; 9 | 10 | yield put(push(`/bounty/draft/${bounty.uid}`)); 11 | } 12 | 13 | export function* watchBountyCreated() { 14 | yield takeLatest([CREATE_DRAFT_SUCCESS], bountyCreated); 15 | } 16 | 17 | export default [watchBountyCreated]; 18 | -------------------------------------------------------------------------------- /src/containers/Login/components/index.js: -------------------------------------------------------------------------------- 1 | import WalletRequired from './WalletRequired'; 2 | import UnlockWallet from './UnlockWallet'; 3 | import WrongNetwork from './WrongNetwork'; 4 | import SignIn from './SignIn'; 5 | import SigningIn from './SigningIn'; 6 | import AddressMismatch from './AddressMismatch'; 7 | import ErrorModal from './ErrorModal'; 8 | import AddProfileDetails from './AddProfileDetails'; 9 | 10 | export { 11 | WalletRequired, 12 | UnlockWallet, 13 | SignIn, 14 | SigningIn, 15 | AddressMismatch, 16 | ErrorModal, 17 | AddProfileDetails, 18 | WrongNetwork 19 | }; 20 | -------------------------------------------------------------------------------- /src/public-modules/Settings/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootSettingsSelector = state => state.settings; 4 | 5 | export const settingsSelector = createSelector( 6 | rootSettingsSelector, 7 | rootSettings => rootSettings.settings 8 | ); 9 | 10 | export const emailPreferencesSelector = createSelector( 11 | rootSettingsSelector, 12 | rootSettings => rootSettings.emailPreferences 13 | ); 14 | 15 | export const profileImageUploadStateSelector = createSelector( 16 | rootSettingsSelector, 17 | rootSettings => rootSettings.uploadState 18 | ); 19 | -------------------------------------------------------------------------------- /src/hocs/LinkedComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | function LinkedComponentHOC(WrappedComponent) { 5 | const LinkedComponent = props => { 6 | const { history, to, onClick } = props; 7 | 8 | const handler = e => { 9 | e.preventDefault(); 10 | history.push(to); 11 | if (onClick) { 12 | onClick(e); 13 | } 14 | }; 15 | 16 | return ; 17 | }; 18 | 19 | return withRouter(LinkedComponent); 20 | } 21 | 22 | export default LinkedComponentHOC; 23 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ 2 | extensions: ['.js', '.jsx', '.ts', '.tsx'] 3 | }); 4 | 5 | import React from 'react'; 6 | import { configure, addDecorator } from '@storybook/react'; 7 | import { ThemeProvider } from 'emotion-theming'; 8 | import theme from '../src/theme'; 9 | import '../src/styles/flexboxgrid.css'; 10 | import '../src/styles/index.scss'; 11 | import '../src/styles/Toastify.scss'; 12 | 13 | addDecorator(story => {story()}); 14 | 15 | configure(require.context('../src/stories', true, /\.stories\.(js|mdx|tsx)$/), module); 16 | -------------------------------------------------------------------------------- /src/containers/Settings/components/PreferencesToggle.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .body { 4 | border-radius: $base-border-radius; 5 | background: $base-input-background; 6 | } 7 | 8 | .toggleContainer { 9 | align-items: center; 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-between; 13 | margin-bottom: $base-spacing; 14 | padding: $base-spacing $m-spacing; 15 | } 16 | 17 | .toggleLabel { 18 | line-height: 1.4; 19 | } 20 | 21 | .toggle { 22 | display: flex; 23 | justify-content: flex-end; 24 | padding-left: $m-spacing; 25 | } 26 | -------------------------------------------------------------------------------- /src/hocs/FilterNavManager.js: -------------------------------------------------------------------------------- 1 | import { compose } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import { actions as appActions } from 'layout/App/reducer'; 4 | 5 | function FilterNavComponentHOC(WrappedComponent) { 6 | return compose( 7 | connect( 8 | null, 9 | { 10 | initFilterNav: appActions.initializeFilterNav, 11 | showFilterNav: appActions.showFilterNav, 12 | hideFilterNav: appActions.hideFilterNav, 13 | resetFilterNav: appActions.resetFilterNav 14 | } 15 | ) 16 | )(WrappedComponent); 17 | } 18 | 19 | export default FilterNavComponentHOC; 20 | -------------------------------------------------------------------------------- /src/hocs/ModalFormReset.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | function ModalFormReset(WrappedComponent) { 4 | return class ModalFormResetComponent extends Component { 5 | componentDidUpdate(prevProps) { 6 | if (!prevProps.visible && this.props.visible) { 7 | if (this.props.initialValues) { 8 | return this.props.initialize(this.props.initialValues); 9 | } 10 | return this.props.initialize(); 11 | } 12 | } 13 | 14 | render() { 15 | return ; 16 | } 17 | }; 18 | } 19 | 20 | export default ModalFormReset; 21 | -------------------------------------------------------------------------------- /src/containers/Explorer/Explorer.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .explorerContainer { 4 | display: flex; 5 | height: 100%; 6 | } 7 | 8 | .explorerBody { 9 | @media only screen and (min-width: 48em) { 10 | padding: $xl-spacing $base-spacing $xl-spacing calc(#{$filterNav-width-small} + #{$base-spacing}); 11 | } 12 | 13 | @media only screen and (min-width: 65em) { 14 | padding-left: $filterNav-width-large + $m-spacing; 15 | padding-right: $m-spacing; 16 | } 17 | } 18 | 19 | .desktopFilter { 20 | display: none; 21 | @media only screen and (min-width: 48em) { 22 | display: block; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/Leaderboard.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .bodyCard { 4 | height: 100%; 5 | margin-bottom: 0; 6 | position: relative; 7 | 8 | @media only screen and (min-width: 35em) { 9 | min-height: 25rem; 10 | margin-bottom: $xl-spacing; 11 | max-height: 70vh; 12 | } 13 | } 14 | 15 | .bodyClass { 16 | height: 100%; 17 | overflow-y: scroll; 18 | padding: $s-spacing; 19 | transform: translate3d(0,0,1px); 20 | 21 | @media only screen and (min-width: 35em) { 22 | min-height: 25rem; 23 | max-height: 70vh; 24 | padding: $l-spacing; 25 | transform: none; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/public-modules/config.js: -------------------------------------------------------------------------------- 1 | import { flatten, groupBy, keys, values } from 'lodash'; 2 | import relayer from './relayer-config'; 3 | import ipfs from './ipfs'; 4 | const config = require('./config.json'); 5 | const moduleSettings = require(`./${process.env.APP_SETTINGS_FILE}.json`); 6 | 7 | const platforms = moduleSettings.platforms 8 | ? moduleSettings.platforms 9 | : groupBy(value => value, moduleSettings.platform.split(',')); 10 | 11 | export default { 12 | ...config, 13 | ...moduleSettings, 14 | platforms, 15 | platform: flatten(values(platforms)).join(','), 16 | displayPlatforms: keys(platforms), 17 | ...relayer, 18 | ipfs 19 | }; 20 | -------------------------------------------------------------------------------- /src/public-modules/Bounties/constants.js: -------------------------------------------------------------------------------- 1 | import { invert } from 'lodash'; 2 | 3 | export const PAGE_SIZE = 25; 4 | 5 | export const SORT_VALUE = 'usd_price'; 6 | export const SORT_CREATED = 'bounty_created'; 7 | export const SORT_EXPIRY = 'deadline'; 8 | 9 | export const SORT_OPTIONS = [SORT_VALUE, SORT_CREATED, SORT_EXPIRY]; 10 | 11 | export const DIFFICULTY_MAPPING = { 12 | beginner: 0, 13 | intermediate: 1, 14 | advanced: 2 15 | }; 16 | 17 | export const REV_DIFFICULTY_MAPPING = invert(DIFFICULTY_MAPPING); 18 | 19 | export const STAGE_MAPPING = { 20 | draft: 0, 21 | active: 1, 22 | dead: 2, 23 | completed: 3, 24 | expired: 4 25 | }; 26 | -------------------------------------------------------------------------------- /apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: { 3 | name: "bounties-network", 4 | service: { 5 | url: "http://localhost:8080/v1/graphql", 6 | headers: { 7 | Authorization: 8 | "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwdWJsaWNfYWRkcmVzcyI6IjB4YmZlY2VjNDdkZDhiZjVmNjI2NGE5ODMwYTlkMjZlZjM4N2MzOGE2NyIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJ1c2VyIl0sIngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS11c2VyLWlkIjoiOCJ9LCJleHAiOjQ3MjQyNjMyMjB9.uKSLyKZu2B2dDx9KehB00cBEaUWcSWFVrzol7-VO6Hk" 9 | } 10 | }, 11 | includes: ["./src/**/*.tsx", "./src/**/*.ts"] 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/containers/Login/components/SigningIn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Modal } from 'components'; 4 | import intl from 'react-intl-universal'; 5 | 6 | const SigningIn = props => { 7 | const { visible } = props; 8 | 9 | return ( 10 | 11 | 12 | 13 | {intl.get('sections.login.modals.signing_in.title')} 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | SigningIn.propTypes = { 21 | visible: PropTypes.bool 22 | }; 23 | 24 | export default SigningIn; 25 | -------------------------------------------------------------------------------- /src/containers/CreateBounty/selectors.js: -------------------------------------------------------------------------------- 1 | import { map, reject } from 'lodash'; 2 | 3 | export const rootCreateBountySelector = state => state.createBounty; 4 | 5 | export const tokensDropdownDataSelector = state => { 6 | return reject( 7 | item => item.value === '0x0000000000000000000000000000000000000000', 8 | map(value => { 9 | const { token, token_contract, token_symbol } = value; 10 | const name = token.length ? token[0].name : token_symbol; 11 | return { 12 | ...value, 13 | display: `${name}${name && ' — '}${token_contract}`, 14 | value: token_contract 15 | }; 16 | }, state.tokens.tokens) 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/hocs/LoadComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { curry } from 'lodash'; 4 | 5 | function LoadComponent(initiatorProp, WrappedComponent) { 6 | return class Load extends Component { 7 | static propTypes = { 8 | load: PropTypes.func.isRequired 9 | }; 10 | 11 | componentDidMount() { 12 | if (initiatorProp) { 13 | this.props.load(this.props[initiatorProp]); 14 | } else { 15 | this.props.load(); 16 | } 17 | } 18 | 19 | render() { 20 | return ; 21 | } 22 | }; 23 | } 24 | 25 | export default curry(LoadComponent); 26 | -------------------------------------------------------------------------------- /src/containers/Bounty/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootBountyPageSelector = state => state.bountyPageUI; 4 | 5 | export const bountyPageSelector = createSelector( 6 | rootBountyPageSelector, 7 | rootBounty => rootBounty 8 | ); 9 | 10 | export const bountyIdSelector = createSelector( 11 | rootBountyPageSelector, 12 | rootBounty => rootBounty.bountyId 13 | ); 14 | 15 | export const ratingModalSelector = createSelector( 16 | bountyPageSelector, 17 | bountyPage => bountyPage.ratingModal 18 | ); 19 | 20 | export const rejectionModalSelector = createSelector( 21 | bountyPageSelector, 22 | bountyPage => bountyPage.rejectionModal 23 | ); 24 | -------------------------------------------------------------------------------- /src/sagas.js: -------------------------------------------------------------------------------- 1 | import profileSagas from 'containers/Profile/sagas'; 2 | import bountySagas from 'containers/CreateBounty/sagas'; 3 | import bountyPageUISagas from 'containers/Bounty/sagas'; 4 | import loginSagas from 'containers/Login/sagas'; 5 | import settingsSagas from 'containers/Settings/sagas'; 6 | import submissionsPanelSagas from 'containers/SubmissionsPanel/sagas'; 7 | import appSagas from 'layout/App/sagas'; 8 | 9 | // Sagas not from public-modules 10 | const sagaWatchers = [ 11 | ...profileSagas, 12 | ...bountySagas, 13 | ...bountyPageUISagas, 14 | ...loginSagas, 15 | ...settingsSagas, 16 | ...submissionsPanelSagas, 17 | ...appSagas 18 | ]; 19 | 20 | export default sagaWatchers; 21 | -------------------------------------------------------------------------------- /src/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './Loader.module.scss'; 4 | 5 | const Loader = props => ( 6 |
7 |
12 |
13 | ); 14 | 15 | Loader.propTypes = { 16 | className: PropTypes.string, 17 | size: PropTypes.oneOf(['small', 'medium']), 18 | color: PropTypes.oneOf(['blue', 'white']) 19 | }; 20 | 21 | Loader.defaultProps = { 22 | size: 'small', 23 | color: 'blue' 24 | }; 25 | 26 | export default Loader; 27 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Banner from './Banner'; 3 | import LeaderboardCard from './LeaderboardCard'; 4 | import styles from './Leaderboard.module.scss'; 5 | 6 | import { PageCard } from 'explorer-components'; 7 | 8 | const Leaderboard = props => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Leaderboard; 26 | -------------------------------------------------------------------------------- /src/public-modules/Tokens/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import { actionTypes, actions } from 'public-modules/Tokens'; 4 | 5 | const { LOAD_TOKENS } = actionTypes; 6 | const { loadTokensFail, loadTokensSuccess } = actions; 7 | 8 | export function* loadTokens(action) { 9 | try { 10 | let endpoint = 'token'; 11 | const tokens = yield call(request, endpoint, 'GET'); 12 | yield put(loadTokensSuccess(tokens)); 13 | } catch (e) { 14 | yield put(loadTokensFail(e)); 15 | } 16 | } 17 | 18 | export function* watchTokens() { 19 | yield takeLatest(LOAD_TOKENS, loadTokens); 20 | } 21 | 22 | export default [watchTokens]; 23 | -------------------------------------------------------------------------------- /src/layout/App/constants.js: -------------------------------------------------------------------------------- 1 | import { 2 | faUserAlt, 3 | faTrophyAlt, 4 | faTachometer, 5 | faListAlt 6 | } from '@fortawesome/pro-regular-svg-icons'; 7 | 8 | export const NAV_ITEMS = [ 9 | { 10 | icon: faListAlt, 11 | to: '/explorer', 12 | tabKey: 'explorer', 13 | title: 'nav.explorer' 14 | }, 15 | { 16 | icon: faTachometer, 17 | to: '/dashboard', 18 | tabKey: 'dashboard', 19 | title: 'nav.dashboard' 20 | }, 21 | { 22 | icon: faTrophyAlt, 23 | to: '/leaderboard', 24 | tabKey: 'leaderboard', 25 | title: 'nav.leaderboard' 26 | }, 27 | { 28 | icon: faUserAlt, 29 | to: '/profile', 30 | tabKey: 'profile', 31 | title: 'nav.profile' 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /src/hocs/RequireLoginComponent.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .loginWrapper { 4 | height: 100%; 5 | width: 100%; 6 | position: relative; 7 | } 8 | 9 | :global(.modal-open):global(.unfixed) { 10 | .loginWrapper { 11 | position: fixed; 12 | } 13 | } 14 | 15 | .notLoggedInWrapper { 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | height: 100%; 20 | } 21 | 22 | .notLoggedIn { 23 | margin: $s-spacing; 24 | max-width: 38rem;; 25 | } 26 | 27 | .notLoggedInCard { 28 | padding: $l-spacing; 29 | 30 | button { 31 | @media only screen and (min-width: 48em) { 32 | width: auto; 33 | } 34 | } 35 | } 36 | 37 | .subText { 38 | margin: $l-spacing; 39 | } 40 | -------------------------------------------------------------------------------- /src/containers/BountiesPanel/reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | currentTab: 'active' 3 | }; 4 | 5 | const SET_ACTIVE_TAB = 'dashboardUI/bountiesPanel/SET_ACTIVE_TAB'; 6 | 7 | const setActiveTab = tabKey => { 8 | return { type: SET_ACTIVE_TAB, tabKey }; 9 | }; 10 | 11 | function bountiesPanelReducer(state = initialState, action) { 12 | switch (action.type) { 13 | case SET_ACTIVE_TAB: { 14 | const { tabKey } = action; 15 | 16 | return { 17 | ...state, 18 | currentTab: tabKey 19 | }; 20 | } 21 | default: { 22 | return state; 23 | } 24 | } 25 | } 26 | 27 | export const actions = { setActiveTab }; 28 | export const actionTypes = { SET_ACTIVE_TAB }; 29 | export default bountiesPanelReducer; 30 | -------------------------------------------------------------------------------- /src/containers/Profile/components/Elsewhere/Elsewhere.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | @import "../BaseStyles.module.scss"; 3 | 4 | .elsewhere { 5 | @include statContainer; 6 | } 7 | 8 | .bulletPointContainer { 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | 13 | .icon { 14 | color: $brand-grey; 15 | display: inline-block; 16 | margin-right: $base-spacing; 17 | } 18 | 19 | .bulletPoint { 20 | display: flex; 21 | flex-direction: row; 22 | 23 | &:not(:last-child) { 24 | margin-bottom: $base-spacing; 25 | 26 | @media only screen and (min-width: 35em) { 27 | margin-bottom: $m-spacing; 28 | } 29 | } 30 | } 31 | 32 | .bulletPointText { 33 | display: flex; 34 | flex-direction: column; 35 | } 36 | -------------------------------------------------------------------------------- /src/containers/SubmissionsPanel/reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | currentTab: 'received' 3 | }; 4 | 5 | const SET_ACTIVE_TAB = 'dashboardUI/submissionsPanel/SET_ACTIVE_TAB'; 6 | 7 | const setActiveTab = tabKey => { 8 | return { type: SET_ACTIVE_TAB, tabKey }; 9 | }; 10 | 11 | function submissionsPanelReducer(state = initialState, action) { 12 | switch (action.type) { 13 | case SET_ACTIVE_TAB: { 14 | const { tabKey } = action; 15 | 16 | return { 17 | ...state, 18 | currentTab: tabKey 19 | }; 20 | } 21 | default: { 22 | return state; 23 | } 24 | } 25 | } 26 | 27 | export const actions = { setActiveTab }; 28 | export const actionTypes = { SET_ACTIVE_TAB }; 29 | export default submissionsPanelReducer; 30 | -------------------------------------------------------------------------------- /src/explorer-components/FormSection/FormSection.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .section { 4 | padding: $xxl-spacing 0; 5 | 6 | &:first-child { 7 | padding-top: 0; 8 | } 9 | 10 | &:not(:last-child) { 11 | border-bottom: $base-border; 12 | } 13 | } 14 | 15 | .sectionTitle { 16 | line-height: $base-line-height; 17 | letter-spacing: 1px; 18 | padding-bottom: $s-spacing; 19 | } 20 | 21 | .description { 22 | line-height: $base-line-height; 23 | margin-bottom: $s-spacing; 24 | } 25 | 26 | .subText { 27 | line-height: $base-line-height; 28 | padding-bottom: $l-spacing; 29 | } 30 | 31 | .inputGroup { 32 | padding-top: $m-spacing; 33 | 34 | &:not(:last-child) { 35 | padding-bottom: 2 * $xl-spacing; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/explorer-components/SubmissionItem/SubmissionItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .submissionItemRow:last-child { 4 | margin-bottom: $xxl-spacing * 3; 5 | 6 | @media only screen and (min-width: 48em) { 7 | margin-bottom: 0; 8 | } 9 | } 10 | 11 | .details { 12 | margin-top: $m-spacing; 13 | } 14 | 15 | .value { 16 | word-break: normal !important; 17 | text-align: right; 18 | justify-content: flex-end; 19 | padding: $s-spacing 0; 20 | } 21 | 22 | .usd { 23 | padding-bottom: $base-spacing; 24 | } 25 | 26 | .link { 27 | color: inherit; 28 | line-height: $line-height-small; 29 | } 30 | 31 | .link:hover { 32 | color: $brand-blue; 33 | text-decoration: underline; 34 | } 35 | 36 | .labelGroup { 37 | overflow: hidden; 38 | } -------------------------------------------------------------------------------- /src/reducers.js: -------------------------------------------------------------------------------- 1 | import loginReducer from 'containers/Login/reducer'; 2 | import leaderboardUIReducer from 'containers/Leaderboard/reducer'; 3 | import profileUIReducer from 'containers/Profile/reducer'; 4 | import dashboardReducer from 'containers/Dashboard/reducer'; 5 | import bountyPageUIReducer from 'containers/Bounty/reducer'; 6 | import settingsUIReducer from 'containers/Settings/reducer'; 7 | import appReducer from 'layout/App/reducer'; 8 | 9 | const reducers = { 10 | loginContainer: loginReducer, 11 | leaderboardUI: leaderboardUIReducer, 12 | ...dashboardReducer, 13 | bountyPageUI: bountyPageUIReducer, 14 | profileUI: profileUIReducer, 15 | settingsUI: settingsUIReducer, 16 | app: appReducer 17 | }; 18 | 19 | // Reducers not from public-modules 20 | export default reducers; 21 | -------------------------------------------------------------------------------- /src/public-modules/Notification/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { values, orderBy, some } from 'lodash'; 3 | 4 | export const rootNotificationSelector = state => state.notification; 5 | 6 | export const notificationsSelector = createSelector( 7 | rootNotificationSelector, 8 | rootNotification => rootNotification.notifications 9 | ); 10 | 11 | export const notificationsListSelector = createSelector( 12 | rootNotificationSelector, 13 | rootNotification => { 14 | return ( 15 | orderBy(['created'], ['desc'], values(rootNotification.notifications)) || 16 | [] 17 | ); 18 | } 19 | ); 20 | 21 | export const hasUnreadNotifications = createSelector( 22 | notificationsListSelector, 23 | notifications => some(['viewed', false], notifications) 24 | ); 25 | -------------------------------------------------------------------------------- /src/public-modules/Skills/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import { actions } from 'public-modules/Skills'; 4 | import { actionTypes as clientActionTypes } from 'public-modules/Client'; 5 | 6 | const { SET_INITIALIZED } = clientActionTypes; 7 | const { loadSkillsFail, loadSkillsSuccess } = actions; 8 | 9 | export function* loadSkills(action) { 10 | try { 11 | const endpoint = 'user/skills/?limit=2000'; 12 | const skills = yield call(request, endpoint, 'GET'); 13 | yield put(loadSkillsSuccess(skills)); 14 | } catch (e) { 15 | yield put(loadSkillsFail(e)); 16 | } 17 | } 18 | 19 | export function* watchSkills() { 20 | yield takeLatest(SET_INITIALIZED, loadSkills); 21 | } 22 | 23 | export default [watchSkills]; 24 | -------------------------------------------------------------------------------- /src/public-modules/Notification/helpers.ts: -------------------------------------------------------------------------------- 1 | import { userDashboardNotifications_notifications_dashboardnotification } from './__generated__/userDashboardNotifications'; 2 | 3 | export const deserializeNotification = ( 4 | notificationItem: userDashboardNotifications_notifications_dashboardnotification 5 | ) => { 6 | const { 7 | notifications_notification: { 8 | from_user_id, 9 | notification_name, 10 | notification_created 11 | }, 12 | data: { link, bounty_title }, 13 | viewed, 14 | id 15 | } = notificationItem; 16 | return { 17 | from_user: from_user_id, 18 | notification_name, 19 | // make link relative 20 | link: link.replace(new RegExp('^.*//[^/]+'), ''), 21 | bounty_title, 22 | created: notification_created, 23 | viewed, 24 | id 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/containers/SubmissionsPanel/SubmissionsPanel.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .card { 4 | height: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | flex-grow: 1; 8 | } 9 | 10 | .listGroup { 11 | overflow-y: scroll; 12 | } 13 | 14 | .loadMoreButton button { 15 | margin-top: $l-spacing; 16 | margin-bottom: $xxl-spacing * 3; 17 | text-align: center; 18 | width: 100%; 19 | 20 | @media only screen and (min-width: 48em) { 21 | margin-bottom: 0; 22 | } 23 | } 24 | 25 | .bodyLoading { 26 | display: flex; 27 | flex-grow: 1; 28 | justify-content: center; 29 | align-items: center; 30 | } 31 | 32 | .zeroState { 33 | max-width: 25rem; 34 | margin-bottom: $xxl-spacing * 3; 35 | 36 | @media only screen and (min-width: 48em) { 37 | margin-bottom: 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/stories/Loading.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { storiesOf } from '@storybook/react'; 4 | 5 | import { Loading, Text } from 'components'; 6 | 7 | storiesOf('Loading', module).add('Loading', () => ( 8 |
9 | 15 | Loading 16 | 17 | 18 | 23 | Loading components will render a simple loading animation. 24 | 25 | 26 |
27 | 28 |
29 |
30 | )); 31 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | toggleValue: 'fulfiller' 3 | }; 4 | 5 | const LEADERBOARD_TOGGLE = 'leaderboard/LEADERBOARD_TOGGLE'; 6 | 7 | function leaderboardToggle() { 8 | return { type: LEADERBOARD_TOGGLE }; 9 | } 10 | 11 | function leaderboardUIReducer(state = initialState, action) { 12 | switch (action.type) { 13 | case LEADERBOARD_TOGGLE: { 14 | const toggleValue = 15 | state.toggleValue === 'fulfiller' ? 'issuer' : 'fulfiller'; 16 | 17 | return { 18 | ...state, 19 | toggleValue 20 | }; 21 | } 22 | default: 23 | return state; 24 | } 25 | } 26 | 27 | export const actions = { 28 | leaderboardToggle 29 | }; 30 | 31 | export const actionTypes = { 32 | LEADERBOARD_TOGGLE 33 | }; 34 | 35 | export default leaderboardUIReducer; 36 | -------------------------------------------------------------------------------- /src/form-components/index.js: -------------------------------------------------------------------------------- 1 | import { FormInput } from 'hocs'; 2 | import { 3 | TextInput, 4 | Textbox, 5 | MarkdownEditor, 6 | SearchSelect, 7 | RadioGroup, 8 | NumberInput, 9 | DatePicker, 10 | Toggle, 11 | Rating, 12 | Checkbox 13 | } from 'components'; 14 | 15 | export const FormTextInput = FormInput(TextInput); 16 | export const FormTextbox = FormInput(Textbox); 17 | export const FormMarkdownEditor = FormInput(MarkdownEditor); 18 | export const FormSearchSelect = FormInput(SearchSelect); 19 | export const FormRadioGroup = FormInput(RadioGroup); 20 | export const FormNumberInput = FormInput(NumberInput); 21 | export const FormDatePicker = FormInput(DatePicker); 22 | export const FormToggle = FormInput(Toggle); 23 | export const FormRating = FormInput(Rating); 24 | export const FormCheckbox = FormInput(Checkbox); 25 | -------------------------------------------------------------------------------- /src/containers/Profile/components/About/About.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | @import "../BaseStyles.module.scss"; 3 | 4 | .about { 5 | @include statContainer; 6 | } 7 | 8 | .bulletPointContainer { 9 | display: flex; 10 | flex-direction: row; 11 | 12 | @media only screen and (min-width: 35em) { 13 | flex-direction: column; 14 | 15 | > div:not(:last-child) { 16 | margin-bottom: $l-spacing; 17 | } 18 | } 19 | } 20 | 21 | .icon { 22 | color: $brand-grey; 23 | margin-top: -$s-spacing; 24 | margin-right: $base-spacing; 25 | } 26 | 27 | .bulletPoint { 28 | display: flex; 29 | flex-direction: row; 30 | width: 50%; 31 | 32 | @media only screen and (min-width: 35em) { 33 | width: 100%; 34 | } 35 | } 36 | 37 | .bulletPointText { 38 | display: flex; 39 | flex-direction: column; 40 | } 41 | -------------------------------------------------------------------------------- /src/public-modules/Languages/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import { actions } from 'public-modules/Languages'; 4 | import { actionTypes as clientActionTypes } from 'public-modules/Client'; 5 | 6 | const { SET_INITIALIZED } = clientActionTypes; 7 | const { loadLanguagesFail, loadLanguagesSuccess } = actions; 8 | 9 | export function* loadLanguages(action) { 10 | try { 11 | const endpoint = 'user/languages/?limit=2000'; 12 | const languages = yield call(request, endpoint, 'GET'); 13 | yield put(loadLanguagesSuccess(languages)); 14 | } catch (e) { 15 | yield put(loadLanguagesFail(e)); 16 | } 17 | } 18 | 19 | export function* watchLanguages() { 20 | yield takeLatest(SET_INITIALIZED, loadLanguages); 21 | } 22 | 23 | export default [watchLanguages]; 24 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/listItems/CommentItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .commentItem { 4 | align-items: flex-start; 5 | display: flex; 6 | flex-direction: column; 7 | padding: $l-spacing 0; 8 | 9 | &:not(:last-child) { 10 | border-bottom: $base-border; 11 | } 12 | 13 | @media only screen and (min-width: 35em) { 14 | margin-left: 0; 15 | margin-right: 0; 16 | } 17 | } 18 | 19 | .commentData { 20 | display: flex; 21 | flex-direction: row; 22 | align-items: flex-start; 23 | } 24 | 25 | .commentContent { 26 | width: 100%; 27 | text-align: left; 28 | line-height: $base-line-height; 29 | padding-left: $xxl-spacing + $base-spacing; 30 | white-space: pre-wrap; 31 | word-break: break-word; 32 | margin-top: -$m-spacing; 33 | } 34 | 35 | .timeStamp { 36 | margin-left: $s-spacing; 37 | } 38 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/NewCommentForm.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .container { 4 | display: flex; 5 | justify-content: center; 6 | } 7 | 8 | .commentFieldInput textarea { 9 | min-height: $base-input-height; 10 | width: 100%; 11 | border-top-right-radius: 0; 12 | border-bottom-right-radius: 0; 13 | border-right: none; 14 | padding: $m-spacing; 15 | resize: none; 16 | 17 | &:focus { 18 | box-shadow: $brand-input-box-shadow; 19 | border: 1px solid $brand-blue; 20 | } 21 | } 22 | 23 | .commentFieldButton { 24 | align-items: center; 25 | justify-content: center; 26 | display: flex; 27 | height: auto; 28 | width: $base-input-height; 29 | vertical-align: bottom; 30 | border-bottom-left-radius: 0; 31 | border-top-left-radius: 0; 32 | 33 | &:hover { 34 | transform: translateY(0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/public-modules/UserInfo/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import config from 'public-modules/config'; 4 | import { actionTypes, actions } from 'public-modules/UserInfo'; 5 | 6 | const { LOAD_USERINFO } = actionTypes; 7 | const { loadUserInfoFail, loadUserInfoSuccess } = actions; 8 | 9 | export function* loadUserInfo(action) { 10 | const { address } = action; 11 | 12 | try { 13 | let endpoint = `user/${address}/profile/?platform__in=${config.platform}`; 14 | const userInfo = yield call(request, endpoint, 'GET'); 15 | yield put(loadUserInfoSuccess(userInfo)); 16 | } catch (e) { 17 | yield put(loadUserInfoFail(e)); 18 | } 19 | } 20 | 21 | export function* watchUserInfo() { 22 | yield takeLatest(LOAD_USERINFO, loadUserInfo); 23 | } 24 | 25 | export default [watchUserInfo]; 26 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/components/LeaderItem.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .leaderboardItem { 4 | align-items: center; 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | 9 | .avatar { 10 | flex: 3; 11 | text-align: left; 12 | 13 | p { 14 | text-overflow: ellipsis; 15 | } 16 | } 17 | 18 | .place { 19 | text-align: left; 20 | margin-right: $l-spacing; 21 | min-width: $m-spacing; 22 | 23 | @media only screen and (min-width: 35em) { 24 | font-size: $h3-font-size; 25 | min-width: $l-spacing; 26 | } 27 | } 28 | 29 | .price { 30 | padding-left: $m-spacing; 31 | } 32 | 33 | .value { 34 | @media only screen and (min-width: 35em) { 35 | font-size: $h3-font-size; 36 | } 37 | } 38 | 39 | .label { 40 | margin-left: 0.2rem; 41 | @media only screen and (min-width: 35em) { 42 | font-size: $body-font-size; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Card/Card.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .card { 4 | box-shadow: $base-box-shadow; 5 | background-color: $brand-white; 6 | border-radius: $base-border-radius; 7 | width: 100%; 8 | } 9 | 10 | .hover { 11 | box-shadow: none; 12 | } 13 | 14 | .hover:hover { 15 | box-shadow: $base-box-shadow; 16 | } 17 | 18 | .header { 19 | border-bottom: $base-border; 20 | padding: 0 $m-spacing; 21 | 22 | @media only screen and (min-width: 48em) { 23 | padding: 0 $l-spacing; 24 | } 25 | } 26 | 27 | .noUnderline { 28 | border-bottom: none; 29 | } 30 | 31 | .headerTitle { 32 | padding: $m-spacing 0; 33 | 34 | @media only screen and (min-width: 48em) { 35 | padding: $l-spacing 0; 36 | } 37 | } 38 | 39 | .body { 40 | padding: $m-spacing; 41 | 42 | @media only screen and (min-width: 48em) { 43 | padding: $l-spacing; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/rollbar.js: -------------------------------------------------------------------------------- 1 | import rollbar from 'rollbar/dist/rollbar.umd.min.js'; 2 | 3 | const rollbarConfig = { 4 | accessToken: process.env.ROLLBAR_ACCESS_KEY_CLIENT, 5 | captureUncaught: true, 6 | captureUnhandledRejections: true, 7 | autoInstrument: true, 8 | payload: { 9 | environment: process.env.NODE_ENV, 10 | client: { 11 | javascript: { 12 | source_map_enabled: true, 13 | code_version: process.env.DIST_VERSION, 14 | // Optionally have Rollbar guess which frames the error was thrown from 15 | // when the browser does not provide line and column numbers. 16 | guess_uncaught_frames: true 17 | } 18 | } 19 | } 20 | }; 21 | 22 | const rollbarClient = rollbar.init(rollbarConfig); 23 | 24 | if (process.env.NODE_ENV === 'development') { 25 | rollbarClient.configure({ enabled: false }); 26 | } 27 | 28 | export default rollbarClient; 29 | -------------------------------------------------------------------------------- /src/public-modules/Client/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootClientSelector = state => state.client; 4 | 5 | export const networkSelector = createSelector( 6 | rootClientSelector, 7 | client => client.network 8 | ); 9 | 10 | export const addressSelector = createSelector(rootClientSelector, client => 11 | client.address.toLowerCase() 12 | ); 13 | 14 | export const walletLockedSelector = createSelector( 15 | rootClientSelector, 16 | client => client.locked 17 | ); 18 | 19 | export const hasWalletSelector = createSelector( 20 | rootClientSelector, 21 | client => client.hasWallet 22 | ); 23 | 24 | export const initializedSelector = createSelector( 25 | rootClientSelector, 26 | client => client.initialized 27 | ); 28 | 29 | export const balanceInfoSelector = createSelector( 30 | rootClientSelector, 31 | client => client.balanceInfo 32 | ); 33 | -------------------------------------------------------------------------------- /src/components/MarkdownEditor/MarkdownEditor.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .markdownEditor { 4 | height: 100%; 5 | width: 100%; 6 | position: relative; 7 | 8 | textarea { 9 | position: relative; 10 | padding-top: 3.35rem; 11 | padding-bottom: 3rem; 12 | 13 | @media only screen and (min-width: 56.25em) { 14 | border-bottom: 1px solid transparent; 15 | border-bottom-right-radius: 0; 16 | border-bottom-left-radius: 0; 17 | } 18 | } 19 | 20 | &::after { 21 | content: ''; 22 | position: absolute; 23 | display: inline-block; 24 | bottom: 2.5rem; 25 | left: 0; 26 | right: 0; 27 | background: linear-gradient( 28 | to bottom, 29 | rgba(255, 255, 255, 0) 0%, 30 | rgba($base-input-background, 1) 100% 31 | ); 32 | height: 6.25rem; 33 | pointer-events: none; 34 | margin: 0 2px 1px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/containers/Profile/components/NetworkStats/NetworkStats.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | @import '../BaseStyles.module.scss'; 3 | 4 | .network { 5 | @include statContainer; 6 | } 7 | 8 | .networkStatsHeader { 9 | display: flex; 10 | flex-direction: row; 11 | margin-bottom: $l-spacing; 12 | 13 | > p { 14 | margin-right: $m-spacing; 15 | } 16 | 17 | > div { 18 | margin-top: -$s-spacing; 19 | } 20 | } 21 | 22 | .networkStatsContainer { 23 | display: flex; 24 | flex-direction: row; 25 | width: 100%; 26 | } 27 | 28 | .networkStatCircle { 29 | display: flex; 30 | flex-direction: column; 31 | padding: 0 $s-spacing; 32 | align-items: center; 33 | width: 33%; 34 | 35 | &Label { 36 | padding-top: $m-spacing; 37 | } 38 | } 39 | 40 | .reviewsModalLink { 41 | text-decoration: underline; 42 | cursor: pointer; 43 | } 44 | 45 | .icon { 46 | padding-left: $s-spacing / 2; 47 | } 48 | -------------------------------------------------------------------------------- /src/components/Search/Search.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .searchContainer { 4 | position: relative; 5 | 6 | .searchIcon { 7 | color: $brand-grey; 8 | display: inline-block; 9 | position: absolute; 10 | text-align: center; 11 | line-height: $base-input-height; 12 | top: 1px; 13 | left: $m-spacing; 14 | } 15 | } 16 | 17 | .searchInput { 18 | background-color: $base-input-background; 19 | border-radius: $base-border-radius; 20 | border: 1px solid $brand-lightGrey; 21 | line-height: $base-input-height; 22 | height: $base-input-height; 23 | width: 100%; 24 | font-size: $body-font-size; 25 | padding: 0 $s-spacing 0 $xxl-spacing; 26 | 27 | &:focus { 28 | background: $brand-white; 29 | border: 1px solid $brand-blue; 30 | box-shadow: $brand-input-box-shadow; 31 | outline: none; 32 | } 33 | 34 | &::placeholder { 35 | color: $brand-grey; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/modals/Modals.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .modalBody { 4 | text-align: left; 5 | } 6 | 7 | .inputGroup { 8 | margin-top: $l-spacing; 9 | } 10 | 11 | .fulfillmentInput { 12 | margin-top: $l-spacing; 13 | } 14 | 15 | .paragraphBreak { 16 | margin-bottom: $l-spacing; 17 | } 18 | 19 | p { 20 | em { 21 | font-weight: 700; 22 | } 23 | 24 | .textHighlight { 25 | color: $brand-purple; 26 | font-weight: 500; 27 | } 28 | } 29 | 30 | .submitError, 31 | .modalSubmitError { 32 | color: $brand-destructive-hover; 33 | padding: $m-spacing; 34 | background: lighten($brand-destructive, 23%); 35 | border-radius: $base-border-radius; 36 | margin-bottom: 0; 37 | display: block; 38 | text-align: center; 39 | width: 100%; 40 | } 41 | 42 | .submitError { 43 | margin-top: $base-spacing; 44 | } 45 | 46 | .modalSubmitError { 47 | margin-bottom: $l-spacing; 48 | } 49 | -------------------------------------------------------------------------------- /src/containers/CreateBounty/handle-choose-template.js: -------------------------------------------------------------------------------- 1 | import intl from 'react-intl-universal'; 2 | 3 | const handleChooseTemplate = ( 4 | change, 5 | setState, 6 | currentCategories 7 | ) => templateName => { 8 | // Overwrite description 9 | change( 10 | 'description', 11 | intl.get( 12 | 'sections.create_bounty.templates.' + templateName.value + '.description' 13 | ) 14 | ); 15 | // Concat categories to current or overwrite 16 | change( 17 | 'categories', 18 | Array.isArray(currentCategories) 19 | ? currentCategories.concat( 20 | intl.get( 21 | 'sections.create_bounty.templates.' + 22 | templateName.value + 23 | '.categories' 24 | ) 25 | ) 26 | : 'sections.create_bounty.templates.' + templateName.value + '.categories' 27 | ); 28 | setState && setState(templateName.value); 29 | }; 30 | 31 | export default handleChooseTemplate; 32 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ProfileAvatar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './ProfileAvatar.module.scss'; 3 | import { Avatar, FullAddressBar, Text } from 'components'; 4 | 5 | const ProfileAvatar = props => { 6 | const { address, img, name, className } = props; 7 | 8 | return ( 9 |
10 | 16 | 22 | {name || '--'} 23 | 24 | 29 |
30 | ); 31 | }; 32 | 33 | export default ProfileAvatar; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bounty Explorer 2 | The Bounty Explorer is a customizable interface to the Bounties Network [StandardBounties](https://github.com/Bounties-Network/StandardBounties) protocol which empowers people all around the world create projects, collaborate, and get paid for doing great work. We host this explorer at https://explorer.bounties.network/. Find out more about us [here](https://bounties.network). 3 | 4 | ![bounty explorer screenshot](https://cdn-images-1.medium.com/max/1600/1*1SoD0_L8pGjo5p6-yPExCQ.jpeg) 5 | 6 | ## Setup 7 | This is a React project, so you can get started by using `npm` or `yarn`, installing the modules from the `package.json` and then running the `start` command: 8 | 9 | ``` 10 | yarn install 11 | yarn start 12 | ``` 13 | 14 | ## Documentation 15 | Our documentation can be found at https://bounties.readme.io/. The Explorer's public modules are documented at https://bounties.readme.io/reference#bounites-sdk-getting-started. 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/public-modules/Authentication/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootAuthSelector = state => state.authentication; 4 | 5 | export const loginStateSelector = createSelector( 6 | rootAuthSelector, 7 | rootAuth => rootAuth.loginState 8 | ); 9 | 10 | export const logoutStateSelector = createSelector( 11 | rootAuthSelector, 12 | rootAuth => rootAuth.logoutState 13 | ); 14 | 15 | export const getCurrentUserStateSelector = createSelector( 16 | rootAuthSelector, 17 | rootAuth => rootAuth.getCurrentUserState 18 | ); 19 | 20 | export const getCurrentUserSelector = createSelector( 21 | rootAuthSelector, 22 | rootAuth => rootAuth.user 23 | ); 24 | 25 | export const getUserAddressSelector = createSelector( 26 | getCurrentUserSelector, 27 | user => (user ? user.public_address : '') 28 | ); 29 | 30 | export const hasUserSignedUp = createSelector( 31 | rootAuthSelector, 32 | rootAuth => rootAuth.signedUp 33 | ); 34 | -------------------------------------------------------------------------------- /src/public-modules/Categories/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import { actions } from 'public-modules/Categories'; 4 | import { actionTypes as clientActionTypes } from 'public-modules/Client'; 5 | import config from 'public-modules/config'; 6 | 7 | const { SET_INITIALIZED } = clientActionTypes; 8 | const { loadCategoriesFail, loadCategoriesSuccess } = actions; 9 | 10 | export function* loadCategories(action) { 11 | try { 12 | const params = { limit: 2000, platform: config.categoryPlatform }; 13 | const endpoint = 'category/'; 14 | const categories = yield call(request, endpoint, 'GET', { params }); 15 | yield put(loadCategoriesSuccess(categories)); 16 | } catch (e) { 17 | yield put(loadCategoriesFail(e)); 18 | } 19 | } 20 | 21 | export function* watchCategories() { 22 | yield takeLatest(SET_INITIALIZED, loadCategories); 23 | } 24 | 25 | export default [watchCategories]; 26 | -------------------------------------------------------------------------------- /src/public-modules/Fulfillments/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { LIMIT } from './constants'; 3 | import config from 'public-modules/config'; 4 | 5 | export const rootFulfillmentsSelector = state => state.fulfillments; 6 | 7 | export const fulfillmentsSelector = createSelector( 8 | rootFulfillmentsSelector, 9 | rootFulfillments => rootFulfillments 10 | ); 11 | 12 | export const fulfillmentsQuerySelector = createSelector( 13 | fulfillmentsSelector, 14 | fulfillmentsState => { 15 | const query = { 16 | limit: LIMIT, 17 | ordering: '-fulfillment_created', 18 | platform__in: config.platform 19 | }; 20 | 21 | if (fulfillmentsState.filters) { 22 | const { fulfiller, issuer, bounty_id } = fulfillmentsState.filters; 23 | query['fulfiller'] = fulfiller; 24 | query['bounty__user__public_address'] = issuer; 25 | query['bounty'] = bounty_id; 26 | } 27 | 28 | return query; 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /src/public-modules/Bounty/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootBountySelector = state => state.bounty; 4 | 5 | export const createDraftStateSelector = createSelector( 6 | rootBountySelector, 7 | rootBounty => rootBounty.createDraftState 8 | ); 9 | 10 | export const stdBountyStateSelector = createSelector( 11 | rootBountySelector, 12 | rootBounty => rootBounty.stdBountyState 13 | ); 14 | 15 | export const getDraftStateSelector = createSelector( 16 | rootBountySelector, 17 | rootBounty => rootBounty.getDraftState 18 | ); 19 | 20 | export const getBountyStateSelector = createSelector( 21 | rootBountySelector, 22 | rootBounty => rootBounty.getBountyState 23 | ); 24 | 25 | export const getBountySelector = createSelector( 26 | rootBountySelector, 27 | rootBounty => rootBounty.bounty 28 | ); 29 | 30 | export const getDraftBountySelector = createSelector( 31 | rootBountySelector, 32 | rootBounty => rootBounty.draftBounty 33 | ); 34 | -------------------------------------------------------------------------------- /src/stories/Cropper.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf, addDecorator } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import { Cropper, Text } from 'components'; 5 | 6 | import { withKnobs, select, text, boolean } from '@storybook/addon-knobs/react'; 7 | import centered from '@storybook/addon-centered'; 8 | 9 | addDecorator(centered); 10 | 11 | storiesOf('Cropper', module) 12 | .addDecorator(withKnobs) 13 | .add('default', () => ( 14 | 19 | )) 20 | .add('with image', () => ( 21 | 29 | )); 30 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | Array.from = require('array-from'); 18 | 19 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 20 | // We don't polyfill it in the browser--this is user's responsibility. 21 | if (process.env.NODE_ENV === 'test') { 22 | require('raf').polyfill(global); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Tabs/Tabs.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .tabs { 4 | width: 100%; 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | 9 | .tab { 10 | display: flex; 11 | align-items: center; 12 | width: auto; 13 | margin-bottom: -$base-border-width; 14 | padding: ($base-spacing * 1.25) $base-spacing; 15 | cursor: pointer; 16 | border-bottom: 2px solid transparent; 17 | z-index: 1; 18 | 19 | &:not(:last-child) { 20 | margin-right: $m-spacing; 21 | } 22 | } 23 | 24 | .notification { 25 | margin-left: $base-spacing; 26 | border-radius: 50px; 27 | background-color: $brand-nearWhite; 28 | padding: $s-spacing $base-spacing; 29 | } 30 | 31 | .notification.blue { 32 | background-color: $brand-blue; 33 | } 34 | 35 | .notification.green { 36 | background-color: $brand-green; 37 | } 38 | 39 | .notification.lightGrey { 40 | background-color: $brand-lightGrey; 41 | } 42 | 43 | .active { 44 | border-bottom: 2px solid $brand-blue; 45 | } 46 | -------------------------------------------------------------------------------- /src/public-modules/FileUpload/sagas.js: -------------------------------------------------------------------------------- 1 | import { call, put, takeLatest } from 'redux-saga/effects'; 2 | import { actionTypes, actions } from 'public-modules/FileUpload'; 3 | import { readFile } from 'public-modules/Utilities/helpers'; 4 | import { addBufferToIPFS } from 'public-modules/Utilities/ipfsClient'; 5 | import { Buffer } from 'buffer'; 6 | 7 | const { UPLOAD_FILE } = actionTypes; 8 | const { uploadFileFail, uploadFileSuccess } = actions; 9 | 10 | export function* uploadFile(action) { 11 | const { file, key } = action; 12 | const reader = yield call(readFile, file); 13 | const buffer = Buffer.from(reader.result); 14 | try { 15 | const ipfsHash = yield call(addBufferToIPFS, file.name, buffer); 16 | yield put(uploadFileSuccess(key, ipfsHash, file.name)); 17 | } catch (e) { 18 | yield put(uploadFileFail(key, e)); 19 | } 20 | } 21 | 22 | export function* watchUpload() { 23 | yield takeLatest(UPLOAD_FILE, uploadFile); 24 | } 25 | 26 | export default [watchUpload]; 27 | -------------------------------------------------------------------------------- /src/containers/Bounty/BountyPageCards.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .tabsContainer { 4 | padding: 1px 0; 5 | position: relative; 6 | margin-left: -$m-spacing; 7 | margin-right: -$m-spacing; 8 | overflow-x: scroll; 9 | 10 | @media only screen and (min-width: 35em) { 11 | margin-left: -$l-spacing; 12 | margin-right: -$l-spacing; 13 | } 14 | 15 | &:not(:last-child):after { 16 | position: absolute; 17 | content: ''; 18 | border-bottom: $base-border-width solid $brand-lightGrey; 19 | width: 100%; 20 | transform: translateX(-50%); 21 | left: 50%; 22 | } 23 | } 24 | 25 | .tab { 26 | flex: 1; 27 | justify-content: center; 28 | padding-top: 0; 29 | padding-bottom: $m-spacing; 30 | margin-right: 0; 31 | 32 | @media only screen and (min-width: 35em) { 33 | padding-bottom: $l-spacing; 34 | } 35 | } 36 | 37 | .tabText { 38 | @media only screen and (min-width: 35em) { 39 | font-size: $h3-font-size; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/public-modules/Transaction/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const rootTransactionSelector = state => state.transaction; 4 | 5 | export const transactionsSelector = createSelector( 6 | rootTransactionSelector, 7 | rootTransactions => rootTransactions.transactions 8 | ); 9 | 10 | export const walkthroughVisibleSelector = createSelector( 11 | rootTransactionSelector, 12 | rootTransactions => rootTransactions.walkthroughVisible 13 | ); 14 | 15 | export const pendingReceiptHashSelector = createSelector( 16 | rootTransactionSelector, 17 | rootTransactions => rootTransactions.pendingReceiptHash 18 | ); 19 | 20 | export const getTransactionSelector = txHash => { 21 | return createSelector( 22 | transactionsSelector, 23 | transactions => transactions[txHash] 24 | ); 25 | }; 26 | 27 | export const transactionsInitiatedSelector = createSelector( 28 | rootTransactionSelector, 29 | rootTransactions => rootTransactions.transactionsInitiated 30 | ); 31 | -------------------------------------------------------------------------------- /src/components/PageBanner/PageBanner.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .pageBanner { 4 | background-color: #e5e5e7; 5 | overflow: hidden; 6 | transition: height 0.2s ease-out; 7 | } 8 | 9 | .pageBannerWrapper { 10 | display: flex; 11 | 12 | // on mobile side spacing defaults to 1rem like most components 13 | padding: $m-spacing; 14 | 15 | // on desktop, the wrapClass prop drives edge padding and max width 16 | @media only screen and (min-width: 48em) { 17 | padding: $m-spacing 0; 18 | } 19 | } 20 | 21 | .pageBannerContent { 22 | flex: 1; 23 | } 24 | 25 | .pageBanner-dismissable { 26 | .pageBannerContent { 27 | padding-right: $m-spacing; 28 | } 29 | } 30 | 31 | .pageBannerClose { 32 | display: flex; 33 | align-items: center; 34 | height: $body-font-size; 35 | } 36 | 37 | .closeIcon { 38 | top: $m-spacing; 39 | cursor: pointer; 40 | color: $brand-grey; 41 | font-size: 1.2em; 42 | 43 | &:hover { 44 | color: $brand-blue; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "paths": { 5 | "*": ["*"] 6 | }, 7 | "rootDir": "src", 8 | "outDir": "build/dist", 9 | "target": "esnext", 10 | "module": "esnext", 11 | "jsx": "preserve", 12 | "lib": ["dom", "es2017"], 13 | "moduleResolution": "node", 14 | "allowJs": true, 15 | "noEmit": true, 16 | "pretty": true, 17 | "allowSyntheticDefaultImports": true, 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "removeComments": false, 23 | "preserveConstEnums": true, 24 | "sourceMap": true, 25 | "forceConsistentCasingInFileNames": true, 26 | "esModuleInterop": true, 27 | "resolveJsonModule": true, 28 | "isolatedModules": true, 29 | "noImplicitAny": false 30 | }, 31 | "exclude": ["node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest", "src/setupTests.ts"], 32 | "include": ["src"] 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Cropper/Cropper.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .cropper { 4 | } 5 | 6 | .input { 7 | position: absolute; 8 | opacity: 0; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 100%; 13 | cursor: pointer; 14 | } 15 | 16 | .upload { 17 | position: relative; 18 | margin-right: $m-spacing; 19 | margin-bottom: $m-spacing; 20 | } 21 | 22 | .saveButton { 23 | margin-right: $m-spacing; 24 | } 25 | 26 | .croppieWrapper { 27 | margin-right: $l-spacing; 28 | } 29 | 30 | .croppie { 31 | border-radius: $base-border-radius; 32 | display: block; 33 | position: relative; 34 | margin: 0 auto; 35 | width: 150px; 36 | height: 150px; 37 | } 38 | 39 | .circleContent { 40 | margin: 0 auto; 41 | margin-bottom: $m-spacing; 42 | } 43 | 44 | .contentWrapper { 45 | display: flex; 46 | align-items: center; 47 | padding: $l-spacing 0 $xl-spacing; 48 | } 49 | 50 | .inactive { 51 | display: none; 52 | } 53 | 54 | .disabledInput { 55 | cursor: default; 56 | } 57 | -------------------------------------------------------------------------------- /src/containers/Login/components/ErrorModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Modal, Button, Text } from 'components'; 4 | import intl from 'react-intl-universal'; 5 | 6 | const ErrorModal = props => { 7 | const { visible, onClose } = props; 8 | 9 | return ( 10 | 11 | 12 | 13 | 19 | {intl.get('sections.login.modals.error.title')} 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | ErrorModal.propTypes = { 30 | visible: PropTypes.bool, 31 | onClose: PropTypes.func 32 | }; 33 | 34 | export default ErrorModal; 35 | -------------------------------------------------------------------------------- /src/containers/Profile/components/ProfileTabs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './ProfileTabs.module.scss'; 3 | import { Tabs } from 'components'; 4 | import intl from 'react-intl-universal'; 5 | 6 | // TODO: handle too many skills to display 7 | 8 | const ProfileTabs = props => { 9 | const { currentTab, setActiveTab, issuedCount, fulfilledCount } = props; 10 | 11 | return ( 12 | 18 | 19 | {intl.get('sections.profile.tabs.bounties')} 20 | 21 | 26 | {intl.get('sections.profile.tabs.submissions')} 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default ProfileTabs; 33 | -------------------------------------------------------------------------------- /src/containers/Profile/components/Skills/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Skills.module.scss'; 3 | import { Pill, Text } from 'components'; 4 | import { map } from 'lodash'; 5 | import intl from 'react-intl-universal'; 6 | 7 | // TODO: handle too many skills to display 8 | 9 | const Skills = props => { 10 | const { skills } = props; 11 | 12 | const renderSkills = () => { 13 | return map(skill => { 14 | return ( 15 | 16 | {skill} 17 | 18 | ); 19 | }, skills); 20 | }; 21 | 22 | return ( 23 |
24 | 25 | {intl.get('sections.profile.skills.title')} 26 | 27 | 28 | {skills ? ( 29 |
{renderSkills()}
30 | ) : ( 31 | 'N/A' 32 | )} 33 |
34 | ); 35 | }; 36 | 37 | export default Skills; 38 | -------------------------------------------------------------------------------- /src/components/ToastContainer/ToastContainer.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .baseToast { 4 | display: flex; 5 | border-right: $base-border-width solid rgba($brand-black, 0.10); 6 | padding: $base-spacing 0; 7 | justify-content: space-between; 8 | cursor: default; 9 | } 10 | 11 | .icon { 12 | font-size: $h3-font-size; 13 | padding: 0 $m-spacing; 14 | } 15 | 16 | .link { 17 | margin-right: 10px; 18 | text-decoration: underline; 19 | text-align: right; 20 | } 21 | 22 | .closeIcon { 23 | color: $brand-white; 24 | font-size: $base-font-size; 25 | padding: $m-spacing; 26 | align-self: stretch; 27 | 28 | &:hover { 29 | background: rgba($brand-black, 0.10); 30 | } 31 | } 32 | 33 | .linkSection { 34 | display: flex; 35 | align-items: center; 36 | padding: $base-spacing; 37 | 38 | a { 39 | margin-right: 10px; 40 | text-decoration: underline; 41 | } 42 | } 43 | 44 | .messageSection { 45 | display: flex; 46 | align-items: center; 47 | margin-right: $xxl-spacing; 48 | } 49 | -------------------------------------------------------------------------------- /src/containers/ActivityPanel/ActivityPanel.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .link { 4 | color: inherit; 5 | &:hover { 6 | text-decoration: none; 7 | color: inherit; 8 | } 9 | } 10 | 11 | .listGroup { 12 | margin: -$base-spacing; 13 | 14 | @media only screen and (min-width: 48em) { 15 | margin: -$m-spacing; 16 | } 17 | } 18 | 19 | .activityListItem { 20 | padding-top: $base-spacing * 1.5; 21 | padding-bottom: $base-spacing * 1.5; 22 | } 23 | 24 | .loadMoreButton button { 25 | margin-top: $l-spacing; 26 | margin-bottom: $xxl-spacing * 3; 27 | text-align: center; 28 | width: 100%; 29 | 30 | @media only screen and (min-width: 48em) { 31 | margin-bottom: 0; 32 | } 33 | } 34 | 35 | .bodyLoading { 36 | display: flex; 37 | flex-grow: 1; 38 | justify-content: center; 39 | align-items: center; 40 | } 41 | 42 | .zeroState { 43 | max-width: 25rem; 44 | margin-bottom: $xxl-spacing * 3; 45 | 46 | @media only screen and (min-width: 48em) { 47 | margin-bottom: 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/public-modules/Utilities/ipfsClient.js: -------------------------------------------------------------------------------- 1 | import ipfsMiniAPI from 'ipfs-mini'; 2 | import config from 'public-modules/config'; 3 | 4 | const ipfsConfig = config.ipfs; 5 | 6 | // console.log(ipfsConfig); 7 | const ipfsMini = new ipfsMiniAPI(ipfsConfig); 8 | 9 | export const addBufferToIPFS = (filename, bufferContent) => 10 | new Promise((resolve, reject) => { 11 | // due to es5 issues - we load this via a CDN 12 | // console.log(ipfsConfig); 13 | const ipfs = window.IpfsApi(ipfsConfig); 14 | 15 | ipfs.add( 16 | [{ path: `/bounties/${filename}`, content: bufferContent }], 17 | (err, response) => { 18 | if (err) { 19 | reject(err); 20 | } 21 | resolve(response[1].hash); 22 | } 23 | ); 24 | }); 25 | 26 | export const addJSON = data => 27 | new Promise((resolve, reject) => { 28 | // console.log(ipfsConfig); 29 | ipfsMini.addJSON(data, (err, response) => { 30 | if (err) { 31 | reject(err); 32 | } 33 | resolve(response); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/containers/Settings/components/PreferencesToggle.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './PreferencesToggle.module.scss'; 4 | import { Text, Toggle } from 'components'; 5 | import { FormInput } from 'hocs'; 6 | 7 | const PreferencesToggle = props => { 8 | const { value, label, onChange, className } = props; 9 | 10 | return ( 11 |
12 |
13 |
14 | 15 | {label} 16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | PreferencesToggle.propTypes = { 27 | text: PropTypes.string, 28 | onChange: PropTypes.func 29 | }; 30 | 31 | PreferencesToggle.defaultProps = {}; 32 | 33 | export default FormInput(PreferencesToggle); 34 | -------------------------------------------------------------------------------- /src/containers/SubmissionsPanel/sagas.js: -------------------------------------------------------------------------------- 1 | import { put, takeLatest, select } from 'redux-saga/effects'; 2 | import { getUserAddressSelector } from 'public-modules/Authentication/selectors'; 3 | import { actionTypes } from './reducer'; 4 | import { actions as fulfillmentsActions } from 'public-modules/Fulfillments'; 5 | 6 | const { SET_ACTIVE_TAB } = actionTypes; 7 | const { 8 | addFulfillerFilter, 9 | addIssuerFilter, 10 | loadFulfillments, 11 | resetFilters 12 | } = fulfillmentsActions; 13 | 14 | export function* loadActiveTab(action) { 15 | const public_address = yield select(getUserAddressSelector); 16 | const { tabKey } = action; 17 | 18 | yield put(resetFilters()); 19 | 20 | if (tabKey === 'received') { 21 | yield put(addIssuerFilter(public_address)); 22 | } else if (tabKey === 'submitted') { 23 | yield put(addFulfillerFilter(public_address)); 24 | } 25 | 26 | yield put(loadFulfillments()); 27 | } 28 | 29 | export function* watchActiveTab() { 30 | yield takeLatest(SET_ACTIVE_TAB, loadActiveTab); 31 | } 32 | 33 | export default [watchActiveTab]; 34 | -------------------------------------------------------------------------------- /src/explorer-components/NotificationItem/NotificationItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .container { 4 | display: flex; 5 | align-items: center; 6 | position: relative; 7 | } 8 | 9 | .text { 10 | text-align: left; 11 | line-height: 1.2; 12 | padding: 0 $base-spacing; 13 | flex-grow: 1; 14 | } 15 | 16 | .time { 17 | padding-left: $base-spacing; 18 | padding-top: $base-spacing; 19 | white-space: nowrap; 20 | width: 100%; 21 | } 22 | 23 | .details { 24 | margin-top: $m-spacing; 25 | } 26 | 27 | .transparent { 28 | background: rgba(255,255,255,.5); 29 | } 30 | 31 | .value { 32 | margin-left: -1rem; 33 | text-align: right; 34 | } 35 | 36 | .notifier { 37 | text-align: center; 38 | &.small { 39 | min-width: $m-spacing; 40 | margin-right: $base-spacing; 41 | } 42 | &.large { 43 | min-width: $xxl-spacing; 44 | } 45 | } 46 | 47 | .iconStyles { 48 | color: $brand-purple; 49 | } 50 | 51 | .message { 52 | display: flex; 53 | flex-direction: row; 54 | } 55 | 56 | .alignLeft { 57 | text-align: left; 58 | } 59 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/modals/ExtendDeadlineErrorModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Button } from 'components'; 3 | import intl from 'react-intl-universal'; 4 | 5 | const ExtendDeadlineErrorModal = props => { 6 | const { onClose, onExtendDeadline, visible } = props; 7 | 8 | return ( 9 | 16 | 17 | 18 | {intl.get('sections.bounty.modals.extend_deadline_error.title')} 19 | 20 | 21 | 22 | 25 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default ExtendDeadlineErrorModal; 34 | -------------------------------------------------------------------------------- /src/stories/Textbox.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { addDecorator, storiesOf } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { 6 | withKnobs, 7 | select, 8 | text, 9 | boolean, 10 | number 11 | } from '@storybook/addon-knobs/react'; 12 | import centered from '@storybook/addon-centered'; 13 | 14 | import { Textbox, Text } from 'components'; 15 | 16 | addDecorator(centered); 17 | 18 | storiesOf('Textbox', module) 19 | .addDecorator(withKnobs) 20 | .add('all', () => ( 21 | 34 | )); 35 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .spinner { 4 | width: 70px; 5 | text-align: center; 6 | display: flex; 7 | justify-content: center; 8 | } 9 | 10 | .spinner > div { 11 | width: 18px; 12 | height: 18px; 13 | background-color: $brand-purple; 14 | 15 | border-radius: 100%; 16 | display: inline-block; 17 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; 18 | animation: sk-bouncedelay 1.4s infinite ease-in-out both; 19 | } 20 | 21 | .spinner .bounce1 { 22 | -webkit-animation-delay: -0.32s; 23 | animation-delay: -0.32s; 24 | } 25 | 26 | .spinner .bounce2 { 27 | -webkit-animation-delay: -0.16s; 28 | animation-delay: -0.16s; 29 | } 30 | 31 | @-webkit-keyframes sk-bouncedelay { 32 | 0%, 80%, 100% { -webkit-transform: scale(0) } 33 | 40% { -webkit-transform: scale(1.0) } 34 | } 35 | 36 | @keyframes sk-bouncedelay { 37 | 0%, 80%, 100% { 38 | -webkit-transform: scale(0); 39 | transform: scale(0); 40 | } 40% { 41 | -webkit-transform: scale(1.0); 42 | transform: scale(1.0); 43 | } 44 | } -------------------------------------------------------------------------------- /src/styles/storybook.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .sb-page-wrapper { 4 | margin: 56px auto 0; 5 | max-width: 700px; 6 | padding: 0 40px;; 7 | } 8 | 9 | .sb-component-group { 10 | margin-bottom: 56px; 11 | padding: 32px; 12 | border: 1px solid #e0e0e0; 13 | border-radius: 3px; 14 | } 15 | 16 | .sb-button-group button:not(:last-child) { 17 | display: block; 18 | margin-bottom: $m-spacing; 19 | } 20 | 21 | .sb-component-container:not(:last-child) { 22 | margin-bottom: 24px; 23 | } 24 | 25 | .sb-component-group-heading { 26 | margin-bottom: 40px; 27 | padding-bottom: 24px; 28 | border-bottom: 1px solid #e0e0e0; 29 | } 30 | 31 | .sb-component-group-subheading { 32 | margin: 48px 0 32px; 33 | padding-bottom: 16px; 34 | border-bottom: 1px solid #e0e0e0; 35 | } 36 | 37 | .sb-component-group-description { 38 | margin-bottom: 24px; 39 | } 40 | 41 | code { 42 | background: $brand-nearWhite; 43 | border-radius: $base-border-radius; 44 | color: $brand-darkGrey; 45 | font-family: monospace; 46 | margin: 0 $s-spacing; 47 | padding: $s-spacing; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/CardNotification/CardNotification.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .cardNotificationContainer { 4 | height: 45px; 5 | cursor: pointer; 6 | display: flex; 7 | justify-content: space-between; 8 | padding: 5px 20px; 9 | min-width: 600px; 10 | border-bottom: 1px solid $brand-lightGrey; 11 | } 12 | 13 | .cardNotificationContainer:hover { 14 | background-color: $brand-nearWhite; 15 | } 16 | 17 | .leftColumn { 18 | width: 90%; 19 | height: 100%; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .notificationDot { 25 | width: 25px; 26 | height: 100%; 27 | display: flex; 28 | align-items: center; 29 | color: $brand-red; 30 | font-size: 10px; 31 | } 32 | 33 | .profilePic { 34 | display: flex; 35 | align-content: center; 36 | margin-right: 10px; 37 | } 38 | 39 | .textArea { 40 | display: flex; 41 | } 42 | 43 | .textCell { 44 | padding-left: 5px; 45 | padding-right: 5px; 46 | width: fit-content; 47 | } 48 | 49 | .rightColumn { 50 | width: 10%; 51 | display: flex; 52 | align-items: center; 53 | padding-right: 5px; 54 | } 55 | -------------------------------------------------------------------------------- /src/containers/Login/components/SignIn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Modal, Button } from 'components'; 4 | import intl from 'react-intl-universal'; 5 | 6 | const SignIn = props => { 7 | const { visible, onClose, signIn } = props; 8 | 9 | return ( 10 | 11 | 12 | 13 | {intl.get('sections.login.modals.sign_in.title')} 14 | 15 | 16 | 17 | 18 | {intl.get('sections.login.modals.sign_in.description')} 19 | 20 | 21 | 22 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | SignIn.propTypes = { 31 | visible: PropTypes.bool, 32 | onClose: PropTypes.func, 33 | signIn: PropTypes.func 34 | }; 35 | 36 | export default SignIn; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bounties Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/containers/Profile/components/SEOHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | 4 | const SEOHeader = props => { 5 | const { user } = props; 6 | 7 | if (!user) { 8 | return null; 9 | } 10 | 11 | const title = user.name 12 | ? `Bounties Explorer Profile ${user.name}` 13 | : 'Bounties Explorer Profile'; 14 | 15 | return ( 16 | 17 | {title} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default SEOHeader; 33 | -------------------------------------------------------------------------------- /src/components/ListGroup/ListGroup.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .listItem { 4 | padding: $m-spacing $m-spacing; 5 | position: relative; 6 | border-radius: $base-border-radius; 7 | margin-top: -$base-border-width; 8 | 9 | &:not(:last-child):after { 10 | position: absolute; 11 | content: ''; 12 | bottom: 0; 13 | transform: translateX(-50%); 14 | left: 50%; 15 | } 16 | } 17 | 18 | .white { 19 | border-color: transparent; 20 | } 21 | 22 | .nearWhite { 23 | &:not(:last-child):after { 24 | border-bottom: $base-border-width solid $brand-nearWhite; 25 | } 26 | } 27 | 28 | .lightGrey { 29 | &:not(:last-child):after { 30 | border-bottom: $base-border-width solid $brand-lightGrey; 31 | } 32 | } 33 | 34 | .fullBorder { 35 | &:not(:last-child):after { 36 | width: 100%; 37 | } 38 | } 39 | 40 | .shortBorder { 41 | &:not(:last-child):after { 42 | width: calc(100% - #{$m-spacing}); 43 | } 44 | } 45 | 46 | .listItem.itemHover:hover { 47 | background-color: $brand-nearWhite; 48 | border: none; 49 | cursor: pointer; 50 | } 51 | 52 | .listGroup { 53 | display: block; 54 | } 55 | -------------------------------------------------------------------------------- /src/containers/BountiesPanel/BountiesPanel.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .link { 4 | color: inherit; 5 | &:hover { 6 | text-decoration: none; 7 | color: inherit; 8 | } 9 | } 10 | 11 | .listGroup { 12 | margin: -$base-spacing; 13 | 14 | @media only screen and (min-width: 48em) { 15 | margin: -$m-spacing; 16 | } 17 | } 18 | 19 | .loadMoreButton button { 20 | margin-top: $l-spacing; 21 | margin-bottom: $xxl-spacing * 3; 22 | text-align: center; 23 | width: 100%; 24 | 25 | @media only screen and (min-width: 48em) { 26 | margin-bottom: 0; 27 | } 28 | } 29 | 30 | .bodyLoading { 31 | display: flex; 32 | flex-grow: 1; 33 | justify-content: center; 34 | align-items: center; 35 | } 36 | 37 | .zeroState { 38 | max-width: 25rem; 39 | margin-bottom: $xxl-spacing * 3; 40 | 41 | @media only screen and (min-width: 48em) { 42 | margin-bottom: 0; 43 | } 44 | } 45 | 46 | .bountiesPanelCardHeader { 47 | display: flex; 48 | justify-content: space-between; 49 | align-items: center; 50 | padding: $l-spacing 0; 51 | } 52 | 53 | .decoratedLink { 54 | text-decoration: underline; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Rating/Rating.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | $unchecked-color: $brand-lightGrey; 4 | $checked-color: $brand-blue; 5 | 6 | .starGroup { 7 | font-size: $body-font-size; 8 | 9 | /* flip the order so we can use the + and ~ combinators */ 10 | unicode-bidi: bidi-override; 11 | direction: rtl; 12 | 13 | & > input { 14 | display: none; 15 | 16 | & + label { 17 | background-color: $unchecked-color; 18 | border-radius: 50px; 19 | color: $brand-white; 20 | display: inline-block; 21 | padding: 0 ($base-spacing * 1.5); 22 | cursor: pointer; 23 | 24 | &:not(:last-child) { 25 | margin-right: $s-spacing; 26 | } 27 | } 28 | 29 | &:checked ~ label { 30 | background-color: $checked-color; 31 | } 32 | } 33 | 34 | /* the hidden clearer */ 35 | & .starClear + label { 36 | display: none; 37 | } 38 | 39 | &:hover > input + label:hover ~ label, 40 | &:hover > input + label:hover { 41 | background-color: $checked-color; 42 | } 43 | } 44 | 45 | .inputHelpText { 46 | padding-top: $base-spacing; 47 | line-height: $line-height-small; 48 | } 49 | -------------------------------------------------------------------------------- /src/containers/Login/sagas.js: -------------------------------------------------------------------------------- 1 | import { hasUserSignedUp } from 'public-modules/Authentication/selectors'; 2 | import { takeLatest, put, select } from 'redux-saga/effects'; 3 | import { actions as authActions } from 'public-modules/Authentication'; 4 | import { actionTypes as authActionTypes } from 'public-modules/Authentication'; 5 | import { actionTypes as settingsActionTypes } from 'public-modules/Settings'; 6 | import { actions } from './reducer'; 7 | 8 | const { LOGIN_SUCCESS } = authActionTypes; 9 | const { SAVE_SETTINGS_SUCCESS } = settingsActionTypes; 10 | const { resetLoginState } = authActions; 11 | const { showLogin, setStage } = actions; 12 | 13 | function* loginSuccess() { 14 | const signedUp = yield select(hasUserSignedUp); 15 | yield signedUp ? put(showLogin(false)) : put(setStage('profile')); 16 | } 17 | 18 | function* saveSettings() { 19 | yield put(resetLoginState()); 20 | } 21 | 22 | export function* watchLogin() { 23 | yield takeLatest(LOGIN_SUCCESS, loginSuccess); 24 | } 25 | 26 | export function* watchSaveSettings() { 27 | yield takeLatest(SAVE_SETTINGS_SUCCESS, saveSettings); 28 | } 29 | 30 | export default [watchLogin, watchSaveSettings]; 31 | -------------------------------------------------------------------------------- /src/containers/Profile/selectors.js: -------------------------------------------------------------------------------- 1 | import { getCurrentUserSelector } from 'public-modules/Authentication/selectors'; 2 | import { createSelector } from 'reselect'; 3 | import { isEmpty, reduce } from 'lodash'; 4 | 5 | export const profileUISelector = state => state.profileUI; 6 | 7 | const profileSections = [ 8 | ['small_profile_image_url'], 9 | ['name', 'organization', 'languages'], 10 | ['skills'], 11 | ['website', 'twitter', 'github', 'linkedin'], 12 | ['email'] 13 | ]; 14 | 15 | export const profileStrengthSelector = createSelector( 16 | getCurrentUserSelector, 17 | currentUser => { 18 | return Math.round( 19 | (reduce( 20 | (sectionsFilled, sectionFields) => { 21 | return ( 22 | sectionsFilled + 23 | (reduce( 24 | (fieldsFilled, field) => 25 | !isEmpty(currentUser[field]) || fieldsFilled, 26 | false, 27 | sectionFields 28 | ) 29 | ? 1 30 | : 0) 31 | ); 32 | }, 33 | 0, 34 | profileSections 35 | ) / 36 | profileSections.length) * 37 | 100 38 | ); 39 | } 40 | ); 41 | -------------------------------------------------------------------------------- /src/components/Network/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './Network.module.scss'; 4 | import { Pill } from 'components'; 5 | 6 | const Network = props => { 7 | const { network, className, theme } = props; 8 | 9 | let networkName = 'Unknown Network'; 10 | let circleStyle = styles.unknownCircle; 11 | if (network === 'rinkeby') { 12 | circleStyle = styles.rinkebyCircle; 13 | networkName = 'Rinkeby Network'; 14 | } 15 | 16 | if (network === 'mainNet') { 17 | networkName = 'Main Ethereum Network'; 18 | circleStyle = styles.mainnetCircle; 19 | } 20 | 21 | return ( 22 | 26 | 27 | {networkName} 28 | 29 | ); 30 | }; 31 | 32 | Network.propTypes = { 33 | network: PropTypes.oneOf(['rinkeby', 'mainNet']), 34 | theme: PropTypes.oneOf(['light', 'dark']), 35 | className: PropTypes.string 36 | }; 37 | 38 | Network.defaultProps = { 39 | network: 'mainNet', 40 | theme: 'dark' 41 | }; 42 | 43 | export default Network; 44 | -------------------------------------------------------------------------------- /src/components/TextInput/TextInput.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .container { 4 | position: relative; 5 | } 6 | 7 | .loader { 8 | position: absolute; 9 | top: 0.25rem; 10 | right: 0.5rem; 11 | } 12 | 13 | .textInput { 14 | height: $base-input-height; 15 | background-color: $base-input-background; 16 | border-radius: $base-border-radius; 17 | border: $base-border; 18 | font-size: $body-font-size; 19 | line-height: normal; 20 | padding: 0 $base-spacing; 21 | width: 100%; 22 | 23 | &:focus { 24 | background: $brand-white; 25 | box-shadow: $brand-input-box-shadow; 26 | border: 1px solid $brand-blue; 27 | outline: none; 28 | } 29 | 30 | &::placeholder { 31 | color: $brand-grey; 32 | } 33 | } 34 | 35 | .disabled { 36 | background-color: $brand-nearWhite; 37 | border: none; 38 | 39 | &::placeholder { 40 | color: darken($brand-nearWhite, 10%); 41 | } 42 | } 43 | 44 | .error { 45 | border: 1px solid $brand-destructive; 46 | box-shadow: $brand-input-box-destructive-shadow; 47 | outline: none; 48 | } 49 | 50 | .inputHelpText { 51 | padding-top: $base-spacing; 52 | line-height: $line-height-small; 53 | } 54 | -------------------------------------------------------------------------------- /src/containers/Settings/Settings.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .cardContent { 4 | text-align: left; 5 | padding: 0; 6 | 7 | &:not(:first-of-type) { 8 | margin-bottom: 0; 9 | } 10 | 11 | @media only screen and (min-width: 48em) { 12 | margin-bottom: $xxl-spacing; 13 | padding: $xl-spacing; 14 | } 15 | } 16 | 17 | .hide { 18 | display: none; 19 | } 20 | 21 | .input:not(:last-child) { 22 | padding-bottom: $l-spacing; 23 | 24 | @media only screen and (min-width: 48em) { 25 | padding-bottom: 0; 26 | } 27 | } 28 | 29 | .markdownEditor { 30 | height: 15em; 31 | } 32 | 33 | .submitError { 34 | margin-top: $base-spacing; 35 | } 36 | 37 | .buttonContainer { 38 | display: block; 39 | border-top: $base-border; 40 | text-align: center; 41 | margin-left: -$m-spacing; 42 | margin-right: -$m-spacing; 43 | padding-top: $xl-spacing; 44 | padding-bottom: $xl-spacing; 45 | margin-bottom: -$m-spacing; 46 | 47 | @media only screen and (min-width: 48em) { 48 | margin-left: -($xxl-spacing + $m-spacing); 49 | margin-right: -($xxl-spacing + $m-spacing); 50 | margin-bottom: -$l-spacing; 51 | padding-bottom: 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/explorer-components/StagePill/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Pill } from 'components'; 4 | import { 5 | DRAFT, 6 | ACTIVE, 7 | DEAD, 8 | COMPLETED, 9 | EXPIRED, 10 | STAGE_VALUES 11 | } from 'public-modules/Bounty/constants'; 12 | import intl from 'react-intl-universal'; 13 | 14 | const StagePill = props => { 15 | const { stage } = props; 16 | 17 | let textColor = 'white'; 18 | let backgroundColor = 'green'; 19 | 20 | if (stage === DRAFT) { 21 | backgroundColor = 'orange'; 22 | } 23 | 24 | if (stage === ACTIVE) { 25 | backgroundColor = 'green'; 26 | } 27 | 28 | if (stage === DEAD) { 29 | backgroundColor = 'blue'; 30 | } 31 | 32 | if (stage === EXPIRED) { 33 | backgroundColor = 'red'; 34 | } 35 | 36 | if (stage === COMPLETED) { 37 | backgroundColor = 'purple'; 38 | } 39 | 40 | return ( 41 | 42 | {intl.get(STAGE_VALUES[stage])} 43 | 44 | ); 45 | }; 46 | 47 | StagePill.propTypes = { 48 | stage: PropTypes.oneOf([DRAFT, ACTIVE, DEAD, COMPLETED, EXPIRED]) 49 | }; 50 | 51 | export default StagePill; 52 | -------------------------------------------------------------------------------- /src/containers/Profile/Profile.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .profileContainer { 4 | height: 100%; 5 | } 6 | 7 | .profileDetails { 8 | background: $brand-white; 9 | } 10 | 11 | .profileBountiesContainer { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: stretch; 15 | height: 100%; 16 | } 17 | 18 | .profileBounties { 19 | align-items: stretch; 20 | background: $brand-nearWhite; 21 | border-top: $base-border; 22 | display: flex; 23 | position: relative; 24 | flex-direction: row; 25 | height: 100%; 26 | } 27 | 28 | .zeroStateCentered { 29 | flex-grow: 1; 30 | display: flex; 31 | align-items: center; 32 | } 33 | 34 | .centeredItem { 35 | margin: 0 auto; 36 | } 37 | 38 | .explorerBody { 39 | @media only screen and (min-width: 48em) { 40 | padding: $xl-spacing $base-spacing $xl-spacing calc(#{$filterNav-width-small} + #{$base-spacing}); 41 | } 42 | 43 | @media only screen and (min-width: 65em) { 44 | padding-left: $filterNav-width-large + $m-spacing; 45 | padding-right: $m-spacing; 46 | } 47 | } 48 | 49 | .desktopFilter { 50 | display: none; 51 | @media only screen and (min-width: 48em) { 52 | display: block; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/hocs/CurrencyModifier.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import config from 'public-modules/config'; 3 | import { isNumber } from 'utils/helpers'; 4 | import { assign } from 'lodash'; 5 | import { Currency } from 'components'; 6 | 7 | function CurrencyModifierHOC(WrappedComponent) { 8 | return props => { 9 | let updatedProps = assign(Currency.defaultProps, props); 10 | 11 | if (config.defaultToken) { 12 | const { primaryValue, secondaryValue } = updatedProps; 13 | 14 | if (isNumber(primaryValue) && isNumber(secondaryValue)) { 15 | const { 16 | primaryCurrency, 17 | primaryDecimals, 18 | secondaryCurrency, 19 | secondaryDecimals 20 | } = updatedProps; 21 | 22 | updatedProps = assign(updatedProps, { 23 | primaryValue: secondaryValue, 24 | primaryCurrency: secondaryCurrency, 25 | primaryDecimals: secondaryDecimals, 26 | secondaryValue: primaryValue, 27 | secondaryCurrency: primaryCurrency, 28 | secondaryDecimals: primaryDecimals 29 | }); 30 | } 31 | } 32 | 33 | return ; 34 | }; 35 | } 36 | 37 | export default CurrencyModifierHOC; 38 | -------------------------------------------------------------------------------- /src/explorer-components/PageCard/PageCard.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .pageCard { 4 | height: 100%; 5 | width: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | 9 | .card { 10 | border-radius: 0; 11 | } 12 | 13 | .body { 14 | border-radius: 0; 15 | } 16 | } 17 | 18 | .headerWrapper { 19 | text-align: center; 20 | padding-top: $l-spacing; 21 | padding-bottom: $l-spacing; 22 | @include banner-gradient; 23 | 24 | @media only screen and (min-width: 48em) { 25 | padding-bottom: $xl-spacing * 2; 26 | } 27 | } 28 | 29 | .contentWrapper { 30 | } 31 | 32 | .header { 33 | padding: $xxl-spacing 0; 34 | } 35 | 36 | .title { 37 | line-height: $line-height-small; 38 | overflow-wrap: break-word; 39 | word-wrap: break-word; 40 | word-break: break-word; 41 | hyphens: auto; 42 | } 43 | 44 | .content { 45 | margin-bottom: $l-spacing; 46 | border-radius: 0; 47 | 48 | @media only screen and (min-width: 48em) { 49 | border-radius: $base-border-radius; 50 | margin-top: -$xxl-spacing; 51 | } 52 | } 53 | 54 | .break { 55 | margin: 0 -$l-spacing; 56 | border-bottom: $base-border; 57 | margin-top: $xxl-spacing; 58 | margin-bottom: $l-spacing; 59 | } 60 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/cards/Cards.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .bodyLoading { 4 | display: flex; 5 | flex-grow: 1; 6 | justify-content: center; 7 | align-items: center; 8 | min-height: 15rem; 9 | } 10 | 11 | .zeroState { 12 | max-width: 25rem; 13 | margin: $xxl-spacing auto; 14 | } 15 | 16 | .newCommentForm { 17 | position: relative; 18 | padding: $m-spacing; 19 | margin-left: -$m-spacing; 20 | margin-right: -$m-spacing; 21 | 22 | @media only screen and (min-width: 35em) { 23 | margin-left: -$l-spacing; 24 | margin-right: -$l-spacing; 25 | padding: $m-spacing $l-spacing; 26 | } 27 | } 28 | 29 | .listItem { 30 | padding: $l-spacing $m-spacing; 31 | 32 | @media only screen and (min-width: 35em) { 33 | margin-left: -$l-spacing; 34 | margin-right: -$l-spacing; 35 | padding: $l-spacing; 36 | } 37 | } 38 | 39 | .loadMoreButton { 40 | width: 100%; 41 | text-align: center; 42 | padding-top: $l-spacing; 43 | } 44 | 45 | .bottomBorder { 46 | border-bottom: 0 solid $brand-nearWhite; 47 | position: relative; 48 | margin-right: -$l-spacing; 49 | margin-left: -$l-spacing; 50 | } 51 | 52 | .applicantsTab { 53 | margin-bottom: -$l-spacing; 54 | } 55 | -------------------------------------------------------------------------------- /src/containers/Settings/constants.js: -------------------------------------------------------------------------------- 1 | export const EMAIL_NOTIFICATION_OPTIONS = { 2 | RatingReceived: 'rating_received', 3 | TransferRecipient: 'transfer_recipient', 4 | BountyCommentReceived: 'bounty_comment_received', 5 | BountyCommentReceivedIssuer: 'bounty_comment_received_issuer', 6 | BountyCommentReceivedCommenter: 'fulfillment_comment_received_commenter', 7 | FulfillmentCommentReceived: 'fulfillment_comment_received', 8 | FulfillmentCommentReceivedIssuer: 'fulfillment_comment_received_issuer', 9 | FulfillmentCommentReceivedCommenter: 'fulfillment_comment_received_commenter', 10 | BountyChanged: 'bounty_changed', 11 | BountyChangedApplicant: 'bounty_changed_applicant', 12 | BountyExpired: 'bounty_expired', 13 | FulfillmentUpdatedIssuer: 'fulfillment_updated_issuer', 14 | FulfillmentSubmittedIssuer: 'fulfillment_submitted_issuer', 15 | FulfillmentAcceptedFulfiller: 'fulfillment_accepted_fulfiller', 16 | ContributionReceived: 'contribution_received', 17 | BountyCompleted: 'bounty_completed', 18 | ApplicationReceived: 'application_received', 19 | ApplicationAcceptedApplicant: 'application_accepted_applicant', 20 | ApplicationRejectedApplicant: 'application_rejected_applicant', 21 | activity: 'activity' 22 | }; 23 | -------------------------------------------------------------------------------- /src/public-modules/Review/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | import { actionTypes, actions } from 'public-modules/Review'; 4 | import { Toast } from 'components'; 5 | 6 | const { POST_REVIEW } = actionTypes; 7 | const { postReviewSuccess, postReviewFail } = actions; 8 | 9 | export function* postNewReview(action) { 10 | const { bountyId, bountyPlatform, fulfillmentId, rating, review } = action; 11 | 12 | // for some reason, this only works when I pass a copy of review 13 | const _review = review; 14 | 15 | try { 16 | let endpoint = `bounty/${bountyId}/fulfillment/${fulfillmentId}/review/`; 17 | const review = yield call(request, endpoint, 'POST', { 18 | data: { 19 | rating, 20 | review: _review, 21 | platform: bountyPlatform 22 | } 23 | }); 24 | 25 | yield put(postReviewSuccess(review)); 26 | yield call(Toast, Toast.TYPE.SUCCESS, 'Your rating was submitted', null); 27 | } catch (e) { 28 | console.log(e); 29 | yield put(postReviewFail(e)); 30 | } 31 | } 32 | 33 | export function* watchPostReview() { 34 | yield takeLatest(POST_REVIEW, postNewReview); 35 | } 36 | 37 | export default [watchPostReview]; 38 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/LeaderboardCard.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .listGroup { 4 | } 5 | 6 | .cardBodyLoading { 7 | height: 100%; 8 | min-height: 25rem; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | flex-direction: column; 13 | } 14 | 15 | .loadMoreButton { 16 | margin-top: $xl-spacing; 17 | padding: $m-spacing; 18 | position: relative; 19 | text-align: center; 20 | z-index: 999; 21 | 22 | @media only screen and (min-width: 35em) { 23 | padding: 0; 24 | } 25 | 26 | button { 27 | position: relative; 28 | width: 100%; 29 | z-index: 999; 30 | } 31 | } 32 | 33 | .zeroStateWrapper { 34 | flex-grow: 1; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | } 39 | 40 | .zeroState { 41 | max-width: 25rem; 42 | margin-bottom: $xxl-spacing * 3; 43 | 44 | @media only screen and (min-width: 48em) { 45 | margin-bottom: 0; 46 | } 47 | } 48 | 49 | .platformSelect { 50 | margin-left: auto; 51 | margin-bottom: $m-spacing; 52 | width: 100%; 53 | 54 | @media only screen and (min-width: 48em) { 55 | width: 40%; 56 | } 57 | 58 | @media only screen and (min-width: 65em) { 59 | width: 25%; 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /src/components/Circle/Circle.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .circle { 4 | :global(.identicon) { 5 | width: 100% !important; 6 | height: 100% !important; 7 | border-radius: 50%; 8 | } 9 | overflow: hidden; 10 | border-radius: 50%; 11 | display: flex; 12 | flex-shrink: 0; 13 | align-items: center; 14 | } 15 | 16 | .text { 17 | width: 100%; 18 | text-align: center; 19 | } 20 | 21 | .img { 22 | background-color: $brand-white; 23 | height: 100%; 24 | width: 100%; 25 | border-radius: 50%; 26 | } 27 | 28 | .orange { 29 | background-color: $brand-orange; 30 | } 31 | 32 | .green { 33 | background-color: $brand-green; 34 | } 35 | 36 | .red { 37 | background-color: $brand-red; 38 | } 39 | 40 | .lightGrey { 41 | background-color: $brand-lightGrey; 42 | } 43 | 44 | .nearWhite { 45 | background-color: $brand-nearWhite; 46 | } 47 | 48 | .small { 49 | height: $circle-sm; 50 | width: $circle-sm; 51 | } 52 | 53 | .medium { 54 | height: $circle-md; 55 | width: $circle-md; 56 | } 57 | 58 | .large { 59 | height: $circle-lg; 60 | width: $circle-lg; 61 | } 62 | 63 | .hasBorder { 64 | box-shadow: 0px 0px 0px 2px rgb(255, 255, 255), $base-box-shadow; 65 | } 66 | 67 | .loading { 68 | margin: auto; 69 | } 70 | -------------------------------------------------------------------------------- /src/components/CardNotification/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './CardNotification.module.scss'; 3 | import { shortenAddress } from 'utils/helpers'; 4 | 5 | import { Circle, Text } from 'components'; 6 | 7 | const CardNotification = props => { 8 | const { notificationData } = props; 9 | const { address = '', action = '', date = '' } = notificationData; 10 | 11 | return ( 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 | {shortenAddress(address)} 21 | 22 |
23 |
24 | {action} 25 |
26 |
27 |
28 | 29 |
30 | 31 | {date} 32 | 33 |
34 |
35 | ); 36 | }; 37 | 38 | export default CardNotification; 39 | -------------------------------------------------------------------------------- /src/explorer-components/ApplicantStagePill/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Pill } from 'components'; 4 | import intl from 'react-intl-universal'; 5 | 6 | const ApplicantStagePill = props => { 7 | const { applicationStatus, className } = props; 8 | 9 | let text = intl.get('components.applicant_stage.default'); 10 | let textColor = 'white'; 11 | let backgroundColor = 'orange'; 12 | 13 | if (applicationStatus === 'A') { 14 | text = intl.get('components.applicant_stage.accepted'); 15 | backgroundColor = 'green'; 16 | } 17 | 18 | if (applicationStatus === 'R') { 19 | text = intl.get('components.applicant_stage.declined'); 20 | backgroundColor = 'red'; 21 | } 22 | 23 | if (applicationStatus === 'P') { 24 | text = intl.get('components.applicant_stage.pending'); 25 | backgroundColor = 'orange'; 26 | } 27 | 28 | return ( 29 | 35 | {text} 36 | 37 | ); 38 | }; 39 | 40 | ApplicantStagePill.propTypes = { 41 | applicationStatus: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 42 | className: PropTypes.string 43 | }; 44 | 45 | export default ApplicantStagePill; 46 | -------------------------------------------------------------------------------- /src/containers/Settings/reducer.js: -------------------------------------------------------------------------------- 1 | let initialState = { 2 | smallProfileImageUrl: null, 3 | largeProfileImageUrl: null 4 | }; 5 | 6 | const SET_PROFILE_IMAGE_URLS = 'settingsUI/SET_PROFILE_IMAGE_URLS'; 7 | const RESET_PROFILE_IMAGE_URLS = 'settingsUI/RESET_PROFILE_IMAGE_URLS'; 8 | 9 | function setProfileImageUrls(small, large) { 10 | return { type: SET_PROFILE_IMAGE_URLS, small, large }; 11 | } 12 | 13 | function resetProfileImageUrls() { 14 | return { type: RESET_PROFILE_IMAGE_URLS }; 15 | } 16 | 17 | function settingsUIReducer(state = initialState, action) { 18 | switch (action.type) { 19 | case SET_PROFILE_IMAGE_URLS: { 20 | const { small, large } = action; 21 | 22 | return { 23 | ...state, 24 | smallProfileImageUrl: small, 25 | largeProfileImageUrl: large 26 | }; 27 | } 28 | case RESET_PROFILE_IMAGE_URLS: { 29 | return { 30 | ...state, 31 | smallProfileImageUrl: null, 32 | largeProfileImageUrl: null 33 | }; 34 | } 35 | default: { 36 | return state; 37 | } 38 | } 39 | } 40 | 41 | export const actions = { 42 | setProfileImageUrls, 43 | resetProfileImageUrls 44 | }; 45 | 46 | export const actionTypes = { 47 | SET_PROFILE_IMAGE_URLS, 48 | RESET_PROFILE_IMAGE_URLS 49 | }; 50 | 51 | export default settingsUIReducer; 52 | -------------------------------------------------------------------------------- /src/components/Dropdown/Dropdown.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .container { 4 | outline: none; 5 | .trigger { 6 | outline: none; 7 | } 8 | position: relative; 9 | display: inline-block; 10 | } 11 | 12 | .content { 13 | border: $base-border; 14 | position: absolute; 15 | background-color: $brand-white; 16 | border-radius: $base-border-radius; 17 | z-index: 999; 18 | } 19 | 20 | .trigger { 21 | cursor: pointer; 22 | } 23 | 24 | .contentLeft { 25 | right: 0px; 26 | } 27 | 28 | .content.show { 29 | display: block; 30 | } 31 | 32 | .menuItem { 33 | padding: $base-spacing; 34 | cursor: pointer; 35 | white-space: nowrap; 36 | } 37 | 38 | .menuItem:hover { 39 | color: $brand-blue-hover; 40 | } 41 | 42 | .menuItems { 43 | padding: $base-spacing; 44 | margin: 0px; 45 | display:inline-block; 46 | list-style-type: none; 47 | } 48 | 49 | .faIcon { 50 | margin-right: 10px; 51 | } 52 | 53 | .enter { 54 | opacity: 0; 55 | transform: translateY(-10px); 56 | } 57 | 58 | .enterRight { 59 | opacity: 0; 60 | transform: translateX(-10px); 61 | } 62 | 63 | .enterActiveRight { 64 | opacity: 1; 65 | transform: translateX(0); 66 | transition: all 150ms ease-in; 67 | } 68 | 69 | .enterActive { 70 | opacity: 1; 71 | transform: translateY(0); 72 | transition: all 150ms ease-in; 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Search/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './Search.module.scss'; 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 5 | import { faSearch } from '@fortawesome/pro-regular-svg-icons'; 6 | 7 | class Search extends React.Component { 8 | state = { 9 | searchText: '' 10 | }; 11 | 12 | onSearchChange = e => { 13 | const { value } = e.target; 14 | this.setState({ searchText: value }); 15 | this.props.onChange(value); 16 | }; 17 | 18 | render() { 19 | const { value } = this.props; 20 | const { searchText } = this.state; 21 | 22 | const searchValue = typeof value === 'string' ? value : searchText; 23 | 24 | return ( 25 |
26 | 27 | 28 | 29 | 36 |
37 | ); 38 | } 39 | } 40 | 41 | Search.propTypes = { 42 | onChange: PropTypes.func 43 | }; 44 | 45 | Search.defaultProps = { 46 | onChange: () => {} 47 | }; 48 | 49 | export default Search; 50 | -------------------------------------------------------------------------------- /src/explorer-components/FilterNav/FilterNav.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .filterNav { 4 | background: $brand-white; 5 | padding: $l-spacing; 6 | margin-top: 0; 7 | max-width: $filterNav-width-large; 8 | width: 70vw; 9 | overflow-y: scroll; 10 | top: 0; 11 | bottom: 0; 12 | position: absolute; 13 | 14 | @media only screen and (min-width: 48em) { 15 | width: $filterNav-width-small; 16 | } 17 | 18 | @media only screen and (min-width: 65em) { 19 | width: $filterNav-width-large; 20 | padding: $l-spacing; 21 | } 22 | } 23 | 24 | .fixed { 25 | position: fixed; 26 | @media only screen and (min-width: 48em) { 27 | top: $header-height-sm; 28 | } 29 | @media only screen and (min-width: 65em) { 30 | top: $header-height-lg; 31 | } 32 | } 33 | 34 | 35 | .searchWrapper { 36 | padding-bottom: $l-spacing; 37 | border-bottom: $base-border; 38 | } 39 | 40 | .refineWrapper { 41 | width: 100%; 42 | display: flex; 43 | align-items: center; 44 | margin-top: $l-spacing; 45 | } 46 | 47 | .clearButton { 48 | margin-left: auto; 49 | } 50 | 51 | .stageFilter { 52 | margin-top: $xl-spacing; 53 | } 54 | 55 | .difficultyFilter { 56 | margin-top: $xl-spacing; 57 | } 58 | 59 | .categoryFilter { 60 | margin-top: $xl-spacing; 61 | } 62 | 63 | .groupText { 64 | margin-bottom: $m-spacing; 65 | } 66 | -------------------------------------------------------------------------------- /src/public-modules/hiring_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "hiring", 3 | "postingPlatform": "hiring", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "hiring", 7 | "networkName": "hiring.bounties.network", 8 | "url": { 9 | "mainNet": "https://rinkeby.api.bounties.network", 10 | "rinkeby": "https://rinkeby.api.bounties.network" 11 | }, 12 | "requiredNetwork": "rinkeby", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV2": "0xf209d2b723b6417cbf04c07e733bee776105a073", 16 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 17 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 18 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 19 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 20 | }, 21 | "rinkeby": { 22 | "standardBountiesAddressV2": "0xf209d2b723b6417cbf04c07e733bee776105a073", 23 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 24 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 25 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 26 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/containers/Leaderboard/components/LeaderItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './LeaderItem.module.scss'; 4 | import { Currency, Text } from 'components'; 5 | import { LinkedAvatar } from 'explorer-components'; 6 | 7 | const LeaderItem = props => { 8 | const { place, img, name, address, value, valueLabel } = props; 9 | 10 | return ( 11 |
12 | 13 | {place} 14 | 15 | 16 |
17 | 24 |
25 | 26 | 34 |
35 | ); 36 | }; 37 | 38 | LeaderItem.propTypes = { 39 | place: PropTypes.number, 40 | img: PropTypes.string, 41 | address: PropTypes.string, 42 | usd: PropTypes.string 43 | }; 44 | 45 | LeaderItem.defaultProps = {}; 46 | 47 | export default LeaderItem; 48 | -------------------------------------------------------------------------------- /src/public-modules/FulfillerApplication/sagas.js: -------------------------------------------------------------------------------- 1 | import request from 'utils/request'; 2 | import { call, put, takeLatest } from 'redux-saga/effects'; 3 | 4 | import { actions, actionTypes } from 'public-modules/FulfillerApplication'; 5 | import { getBounty } from 'public-modules/Bounty/sagas'; 6 | import { loadApplicantsSaga } from 'public-modules/Applicants/sagas'; 7 | 8 | const { 9 | createFulfillerApplicationSuccess, 10 | createFulfillerApplicationFail 11 | } = actions; 12 | 13 | const { CREATE_FULFILLER_APPLICATION } = actionTypes; 14 | 15 | export function* createFulfillerApplication(action) { 16 | const { bountyId, message, callback = () => {} } = action; 17 | 18 | let endpoint = `bounty/${bountyId}/application/`; 19 | let methodType = 'POST'; 20 | 21 | try { 22 | yield call(request, endpoint, methodType, { data: { message } }); 23 | 24 | try { 25 | yield getBounty({ id: bountyId }); 26 | yield loadApplicantsSaga({ bountyId }); 27 | } catch (e) {} 28 | 29 | yield put(createFulfillerApplicationSuccess()); 30 | callback(); 31 | } catch (e) { 32 | yield put(createFulfillerApplicationFail(e)); 33 | callback(e); 34 | } 35 | } 36 | 37 | export function* watchCreateFulfillerApplication() { 38 | yield takeLatest(CREATE_FULFILLER_APPLICATION, createFulfillerApplication); 39 | } 40 | 41 | export default [watchCreateFulfillerApplication]; 42 | -------------------------------------------------------------------------------- /src/containers/Settings/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Settings.module.scss'; 3 | import UserSettings from './UserSettings'; 4 | import EmailPreferences from './EmailPreferences'; 5 | import { PageCard } from 'explorer-components'; 6 | import intl from 'react-intl-universal'; 7 | 8 | import Modal from '../../components/Modal'; 9 | import Button from '../../components/Button'; 10 | 11 | class Settings extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 | 20 | 21 | 22 | {intl.get('sections.settings.title')} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | this.setState({ submitNotPressed: false })} 33 | onChange={() => this.setState({ dirty: true })} 34 | /> 35 | 36 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Settings; 43 | -------------------------------------------------------------------------------- /src/stories/Social.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { storiesOf } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | 6 | import { Social, Text } from 'components'; 7 | 8 | storiesOf('Social', module).add('Social', () => ( 9 |
10 | 16 | Social 17 | 18 | 19 | 24 | Social components create links to share the current URL to different 25 | social media sites. 26 | 27 | 28 | 33 | Regular Social component 34 | 35 | 36 | 41 | A regular Social component takes no props and renders 3 links to share the 42 | current page URL. 43 | 44 | 45 |
46 | 47 |
48 |
49 | )); 50 | -------------------------------------------------------------------------------- /src/components/Social/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Social.module.scss'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { 5 | faFacebook, 6 | faTwitter, 7 | faReddit, 8 | faLinkedin 9 | } from '@fortawesome/free-brands-svg-icons'; 10 | 11 | const Social = props => { 12 | const { utm_campaign } = props; 13 | const url_utm_campaign = utm_campaign ? `&utm_campaign=${utm_campaign}` : ''; 14 | const url = source => 15 | encodeURIComponent( 16 | `${window.location.href}?utm_source=${source}${url_utm_campaign}` 17 | ); 18 | 19 | return ( 20 | 38 | ); 39 | }; 40 | 41 | export default Social; 42 | -------------------------------------------------------------------------------- /src/explorer-components/FulfillmentStagePill/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Pill } from 'components'; 4 | import { DEAD, COMPLETED } from 'public-modules/Bounty/constants'; 5 | import intl from 'react-intl-universal'; 6 | 7 | const FulfillmentStagePill = props => { 8 | const { bounty_stage, accepted, className, pending } = props; 9 | 10 | let text; 11 | let backgroundColor; 12 | let textColor = 'white'; 13 | if (pending) { 14 | text = intl.get('components.fulfillment_stage.pending'); 15 | backgroundColor = 'blue'; 16 | } else if (accepted) { 17 | text = intl.get('components.fulfillment_stage.accepted'); 18 | backgroundColor = 'green'; 19 | } else if ( 20 | !accepted && 21 | (bounty_stage === COMPLETED || bounty_stage === DEAD) 22 | ) { 23 | text = intl.get('components.fulfillment_stage.not_accepted'); 24 | backgroundColor = 'orange'; 25 | } else { 26 | return null; 27 | } 28 | 29 | return ( 30 | 36 | {text} 37 | 38 | ); 39 | }; 40 | 41 | FulfillmentStagePill.propTypes = { 42 | accepted: PropTypes.bool, 43 | bounty_stage: PropTypes.number, 44 | className: PropTypes.string 45 | }; 46 | 47 | export default FulfillmentStagePill; 48 | -------------------------------------------------------------------------------- /src/components/Toggle/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Text } from 'components'; 4 | import ToggleComponent from 'react-toggle'; 5 | import '../../styles/Toggle.scss'; 6 | 7 | class Toggle extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | value: props.value || props.defaultValue 12 | }; 13 | } 14 | 15 | onChange = e => { 16 | const value = !this.state.value; 17 | this.setState({ value }); 18 | this.props.onChange(value); 19 | }; 20 | 21 | render() { 22 | const { defaultValue, disabled, label, value } = this.props; 23 | const { value: stateValue } = this.state; 24 | 25 | const currentValue = value || stateValue || defaultValue; 26 | 27 | return ( 28 |
29 | {label ? ( 30 |
31 | {label} 32 |
33 | ) : null} 34 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | Toggle.propTypes = { 45 | onChange: PropTypes.func, 46 | value: PropTypes.bool, 47 | defaultValue: PropTypes.bool 48 | }; 49 | 50 | Toggle.defaultProps = { 51 | onChange: () => {}, 52 | defaultValue: false 53 | }; 54 | 55 | export default Toggle; 56 | -------------------------------------------------------------------------------- /src/containers/BountiesPanel/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { rootDraftsSelector } from 'public-modules/Drafts/selectors'; 3 | import { rootBountiesSelector } from 'public-modules/Bounties/selectors'; 4 | 5 | export const rootBountiesPanelSelector = state => state.bountiesPanel; 6 | 7 | export const bountiesPanelSelector = createSelector( 8 | rootBountiesPanelSelector, 9 | bountiesPanel => bountiesPanel 10 | ); 11 | 12 | export const currentTabSelector = createSelector( 13 | bountiesPanelSelector, 14 | bountiesPanel => bountiesPanel.currentTab 15 | ); 16 | 17 | export const tabDataSelector = createSelector( 18 | [rootBountiesSelector, rootDraftsSelector, currentTabSelector], 19 | (bountyState, draftsState, currentTab) => { 20 | const data = { 21 | active: { 22 | list: bountyState.bounties, 23 | count: bountyState.count, 24 | offset: bountyState.offset, 25 | loading: bountyState.loading, 26 | loadingMore: bountyState.loadingMore, 27 | error: bountyState.error 28 | }, 29 | drafts: { 30 | list: draftsState.drafts, 31 | count: draftsState.count, 32 | offset: draftsState.offset, 33 | loading: draftsState.loading, 34 | loadingMore: draftsState.loadingMore, 35 | error: draftsState.error 36 | } 37 | }; 38 | 39 | return data[currentTab]; 40 | } 41 | ); 42 | -------------------------------------------------------------------------------- /src/components/Toast/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { toast as callToast } from 'react-toastify'; 3 | import { ToastContainer } from 'components'; 4 | import { faInfoCircle, faEllipsisH } from '@fortawesome/pro-regular-svg-icons'; 5 | import { 6 | faCheckCircle, 7 | faExclamationTriangle 8 | } from '@fortawesome/pro-light-svg-icons'; 9 | 10 | const NOTIFICATION = 'NOTIFICATION'; 11 | const SUCCESS = 'SUCCESS'; 12 | const ERROR = 'ERROR'; 13 | const TRANSACTION = 'TRANSACTION'; 14 | 15 | const Toast = (type, message, link, onClose) => { 16 | let icon; 17 | let messageType; 18 | if (type === NOTIFICATION) { 19 | icon = faInfoCircle; 20 | messageType = callToast.TYPE.INFO; 21 | } 22 | if (type === SUCCESS) { 23 | icon = faCheckCircle; 24 | messageType = callToast.TYPE.SUCCESS; 25 | } 26 | if (type === ERROR) { 27 | icon = faExclamationTriangle; 28 | messageType = callToast.TYPE.ERROR; 29 | } 30 | if (type === TRANSACTION) { 31 | icon = faEllipsisH; 32 | messageType = callToast.TYPE.WARNING; 33 | } 34 | 35 | return callToast( 36 | , 37 | { 38 | type: messageType, 39 | onClose 40 | } 41 | ); 42 | }; 43 | 44 | Toast.TYPE = { 45 | NOTIFICATION: NOTIFICATION, 46 | SUCCESS: SUCCESS, 47 | ERROR: ERROR, 48 | TRANSACTION: TRANSACTION 49 | }; 50 | 51 | export default Toast; 52 | -------------------------------------------------------------------------------- /src/public-modules/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | INIT_TRANSLATIONS, 3 | UPDATE_LOCALE, 4 | TRANSLATION_LOADED 5 | } from './constants'; 6 | 7 | function initTranslations(locale = null) { 8 | return { 9 | type: INIT_TRANSLATIONS, 10 | locale 11 | }; 12 | } 13 | 14 | function changeLocale(locale) { 15 | return { 16 | type: UPDATE_LOCALE, 17 | locale 18 | }; 19 | } 20 | 21 | function translationLoaded(locale) { 22 | return { 23 | type: TRANSLATION_LOADED, 24 | locale 25 | }; 26 | } 27 | 28 | export const actions = { 29 | initTranslations, 30 | changeLocale, 31 | translationLoaded 32 | }; 33 | 34 | const initialState = { 35 | loading: true, 36 | loaded: false, 37 | currentLocale: 'en-US' 38 | }; 39 | 40 | function I18nReducer(state = initialState, action) { 41 | switch (action.type) { 42 | case INIT_TRANSLATIONS: { 43 | return { 44 | ...state, 45 | loading: true, 46 | loaded: false 47 | }; 48 | } 49 | case UPDATE_LOCALE: { 50 | return { 51 | ...state, 52 | loading: true, 53 | loaded: false 54 | }; 55 | } 56 | case TRANSLATION_LOADED: { 57 | return { 58 | ...state, 59 | loading: false, 60 | loaded: true, 61 | currentLocale: action.locale 62 | }; 63 | } 64 | default: 65 | return state; 66 | } 67 | } 68 | 69 | export default I18nReducer; 70 | -------------------------------------------------------------------------------- /src/containers/Profile/sagas.js: -------------------------------------------------------------------------------- 1 | import { put, takeLatest, select } from 'redux-saga/effects'; 2 | import { profileUISelector } from './selectors'; 3 | import { actionTypes } from './reducer'; 4 | import { actions as reviewsActions } from 'public-modules/Reviews'; 5 | import { actions as bountiesActions } from 'public-modules/Bounties'; 6 | 7 | const { SET_ACTIVE_TAB, TOGGLE_NETWORK_SWITCH } = actionTypes; 8 | const { addIssuerFilter, addFulfillerFilter } = bountiesActions; 9 | const { loadBounties } = bountiesActions; 10 | const { loadReviewsReceived } = reviewsActions; 11 | 12 | export function* loadProfileBounties(action) { 13 | const { tabKey } = action; 14 | const { address } = yield select(profileUISelector); 15 | 16 | if (tabKey === 'issued') { 17 | yield put(addIssuerFilter(address)); 18 | } else { 19 | yield put(addFulfillerFilter(address)); 20 | } 21 | 22 | yield put(loadBounties(true)); 23 | } 24 | 25 | export function* networkSwitchChanged(action) { 26 | const { address, switchValue } = yield select(profileUISelector); 27 | yield put(loadReviewsReceived({ address, reviewType: switchValue })); 28 | } 29 | 30 | export function* watchNetworkSwitch() { 31 | yield takeLatest(TOGGLE_NETWORK_SWITCH, networkSwitchChanged); 32 | } 33 | 34 | export function* watchProfileTab() { 35 | yield takeLatest(SET_ACTIVE_TAB, loadProfileBounties); 36 | } 37 | 38 | export default [watchProfileTab, watchNetworkSwitch]; 39 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/listItems/ApplicantItem.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .applicantionItem { 4 | background-color: $brand-white; 5 | border: $base-border; 6 | border-radius: $base-border-radius; 7 | box-shadow: $base-box-shadow; 8 | margin-bottom: $m-spacing; 9 | margin-left: -$base-spacing; 10 | margin-right: -$base-spacing; 11 | overflow: hidden; 12 | position: relative; 13 | 14 | &:first-child { 15 | margin-top: $m-spacing; 16 | } 17 | } 18 | 19 | .applicationHeader { 20 | align-items: center; 21 | background-color: $brand-nearWhite; 22 | display: flex; 23 | justify-content: space-between; 24 | padding: $m-spacing; 25 | } 26 | 27 | .applicationBody { 28 | padding: $m-spacing; 29 | } 30 | 31 | .applicationActions button { 32 | margin-left: $base-spacing; 33 | } 34 | 35 | .applicationDescription { 36 | padding-bottom: $base-spacing; 37 | } 38 | 39 | .applicationReply { 40 | border-top: $light-border; 41 | padding: $m-spacing; 42 | } 43 | 44 | .replyContent { 45 | width: 100%; 46 | text-align: left; 47 | line-height: $base-line-height; 48 | padding-left: $xxl-spacing + $base-spacing; 49 | white-space: pre-wrap; 50 | word-break: break-word; 51 | margin-top: -$m-spacing; 52 | } 53 | 54 | .declinedNoteText { 55 | background-color: $brand-nearWhite; 56 | padding: $base-spacing; 57 | } 58 | 59 | .faIcon { 60 | margin-right: $base-spacing; 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ProgressBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './ProgressBar.module.scss'; 4 | import { Text } from 'components'; 5 | 6 | const ProgressBar = props => { 7 | const { displayPercent, percent, color, className } = props; 8 | 9 | return ( 10 |
13 |
14 |
18 |
19 |
20 |
21 | {displayPercent && ( 22 | 23 | {`${percent}%`} 24 | 25 | )} 26 |
27 | ); 28 | }; 29 | 30 | ProgressBar.propTypes = { 31 | className: PropTypes.string, 32 | displayPercent: PropTypes.bool, 33 | percent: PropTypes.number, 34 | color: PropTypes.oneOf([ 35 | 'purple', 36 | 'blue', 37 | 'orange', 38 | 'green', 39 | 'red', 40 | 'black', 41 | 'white', 42 | 'defaultGrey', 43 | 'lightGrey', 44 | 'darkGrey' 45 | ]) 46 | }; 47 | 48 | ProgressBar.defaultProps = { 49 | displayPercent: true, 50 | percent: 0, 51 | color: 'purple' 52 | }; 53 | 54 | export default ProgressBar; 55 | -------------------------------------------------------------------------------- /src/public-modules/Leaderboard/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { LIMIT } from './constants'; 3 | import { expandPlatforms } from 'utils/global'; 4 | import config from 'public-modules/config'; 5 | 6 | export const rootLeaderboardSelector = state => state.leaderboard; 7 | 8 | export const leaderboardSelector = createSelector( 9 | rootLeaderboardSelector, 10 | rootLeaderboard => rootLeaderboard 11 | ); 12 | 13 | export const leaderboardStateSelector = createSelector( 14 | leaderboardSelector, 15 | rootLeaderboard => ({ 16 | loading: rootLeaderboard.loading, 17 | loadingMore: rootLeaderboard.loadingMore, 18 | loaded: rootLeaderboard.loaded, 19 | loadingMoreError: rootLeaderboard.loadingMoreError, 20 | error: rootLeaderboard.error 21 | }) 22 | ); 23 | 24 | export const leaderboardPlatformFiltersSelector = createSelector( 25 | leaderboardSelector, 26 | rootLeaderboard => [...rootLeaderboard.platformFilters] 27 | ); 28 | 29 | export const leaderboardQuerySelector = createSelector( 30 | leaderboardSelector, 31 | rootLeaderboard => { 32 | const query = { 33 | limit: LIMIT, 34 | platform__in: rootLeaderboard.platformFilters.size 35 | ? expandPlatforms([...rootLeaderboard.platformFilters]) 36 | : config.platform 37 | }; 38 | if (config.defaultToken) { 39 | query.token = config.defaultToken.address; 40 | } 41 | return query; 42 | } 43 | ); 44 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/listItems/CommentItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './CommentItem.module.scss'; 3 | import { Text } from 'components'; 4 | import { LinkedAvatar } from 'explorer-components'; 5 | import moment from 'moment'; 6 | import showdown from 'showdown'; 7 | const converter = new showdown.Converter({ extensions: ['targetBlank'] }); 8 | converter.setFlavor('github'); 9 | 10 | const CommentItem = props => { 11 | const { name, address, img, text, created } = props; 12 | 13 | const formattedTime = moment.utc(created, 'YYYY-MM-DDThh:mm:ssZ').fromNow(); 14 | 15 | return ( 16 |
  • 17 |
    18 | 26 | 31 | {'﹒ ' + formattedTime} 32 | 33 |
    34 | 35 |
    36 | 37 | {text} 38 | 39 |
    40 |
  • 41 | ); 42 | }; 43 | 44 | CommentItem.propTypes = {}; 45 | 46 | CommentItem.defaultProps = {}; 47 | 48 | export default CommentItem; 49 | -------------------------------------------------------------------------------- /src/public-modules/sa_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "sa", 3 | "postingPlatform": "sa", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "sa", 7 | "networkName": "sa.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/sf_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "sf", 3 | "postingPlatform": "sf", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "sf", 7 | "networkName": "sf.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/bgc_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "bgc", 3 | "postingPlatform": "bgc", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "bgc", 7 | "networkName": "bgc.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/nyc_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "nyc", 3 | "postingPlatform": "nyc", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "nyc", 7 | "networkName": "nyc.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/FileUpload/FileUpload.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .fileUpload { 4 | display: flex; 5 | } 6 | 7 | .fileLabel { 8 | flex-grow: 1; 9 | height: $base-input-height; 10 | background-color: $base-input-background; 11 | border-radius: $base-border-radius; 12 | border-top-right-radius: 0; 13 | border-bottom-right-radius: 0; 14 | line-height: $base-input-height; 15 | border: $base-border; 16 | border-right: none; 17 | display: flex; 18 | align-items: center; 19 | padding: 0 10px; 20 | min-width: 0; 21 | } 22 | 23 | .fileInput { 24 | position: absolute; 25 | top: 0px; 26 | left: 0px; 27 | width: 100%; 28 | height: 100%; 29 | opacity: 0; 30 | cursor: pointer; 31 | 32 | &:disabled { 33 | cursor: default; 34 | } 35 | } 36 | 37 | .button { 38 | border-top-left-radius: 0; 39 | border-bottom-left-radius: 0; 40 | position: relative; 41 | height: $base-input-height; 42 | box-shadow: none; 43 | } 44 | 45 | .nameText { 46 | white-space: nowrap; 47 | overflow: hidden; 48 | text-overflow: ellipsis; 49 | } 50 | 51 | .sizeText { 52 | margin: 0 $base-spacing; 53 | white-space: nowrap; 54 | } 55 | 56 | .fileInfo { 57 | display: flex; 58 | min-width: 0; 59 | align-items: center; 60 | } 61 | 62 | .icon { 63 | cursor: pointer; 64 | } 65 | 66 | .iconDisabled { 67 | cursor: default; 68 | } 69 | 70 | .disabled.fileLabel { 71 | background-color: $brand-grey; 72 | .fileInput { 73 | cursor: default; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/public-modules/Notification/__generated__/userDashboardNotifications.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // This file was automatically generated and should not be edited. 4 | 5 | // ==================================================== 6 | // GraphQL query operation: userDashboardNotifications 7 | // ==================================================== 8 | 9 | export interface userDashboardNotifications_notifications_dashboardnotification_notifications_notification { 10 | __typename: 'notifications_notification'; 11 | notification_name: number; 12 | notification_created: any; 13 | from_user_id: number | null; 14 | platform: string; 15 | } 16 | 17 | export interface userDashboardNotifications_notifications_dashboardnotification { 18 | __typename: 'notifications_dashboardnotification'; 19 | id: number; 20 | is_activity: boolean; 21 | created: any; 22 | data: any | null; 23 | string_data: string; 24 | viewed: boolean; 25 | /** 26 | * An object relationship 27 | */ 28 | notifications_notification: userDashboardNotifications_notifications_dashboardnotification_notifications_notification; 29 | } 30 | 31 | export interface userDashboardNotifications { 32 | /** 33 | * fetch data from the table: "notifications_dashboardnotification" 34 | */ 35 | notifications_dashboardnotification: userDashboardNotifications_notifications_dashboardnotification[]; 36 | } 37 | 38 | export interface userDashboardNotificationsVariables { 39 | platforms?: string[] | null; 40 | } 41 | -------------------------------------------------------------------------------- /src/stories/FullAddressBar.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { storiesOf } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | 6 | import { FullAddressBar, Button, Text } from 'components'; 7 | 8 | storiesOf('FullAddressBar', module).add('FullAddressBar', () => ( 9 |
    10 | 16 | FullAddressBar 17 | 18 | 19 | 24 | FullAddressBar components are useful to represent addresses that can be 25 | copied. 26 | 27 | 28 | 33 | address 34 | 35 | 40 | The address prop will be the address represented by the 41 | component. 42 | 43 | 44 |
    45 | 49 |
    50 |
    51 | )); 52 | -------------------------------------------------------------------------------- /src/public-modules/japan_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "japan", 3 | "postingPlatform": "japan", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "japan", 7 | "networkName": "japan.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/korea_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "korea", 3 | "postingPlatform": "korea", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "korea", 7 | "networkName": "korea.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/paris_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "paris", 3 | "postingPlatform": "paris", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "paris", 7 | "networkName": "paris.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/prague_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "prague", 3 | "postingPlatform": "prague", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "prague", 7 | "networkName": "prague.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/FullAddressBar/FullAddressBar.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .AddressBar { 4 | display: inline-block; 5 | padding: $base-spacing; 6 | position: relative; 7 | border: $base-border-width solid transparent; 8 | border-radius: $base-border-radius; 9 | background-color: lighten($brand-purple, 45%); 10 | 11 | &:hover { 12 | border: $base-border-width solid $brand-purple; 13 | cursor: pointer; 14 | } 15 | } 16 | 17 | .tooltip { 18 | position: absolute; 19 | width: fit-content; 20 | color: $brand-darkGrey; 21 | background: $brand-lightGrey; 22 | margin-left: -$base-spacing; 23 | padding: $base-spacing; 24 | font-size: $body-font-size; 25 | text-align: center; 26 | visibility: hidden; 27 | border-radius: $base-border-radius; 28 | opacity: 0; 29 | transition: opacity 0.25s ease, margin-left 0.25s ease; 30 | } 31 | 32 | @media only screen and (min-width: 35em) { 33 | .tooltip:after { 34 | content: ''; 35 | position: absolute; 36 | top: 50%; 37 | right: 100%; 38 | margin-top: -$s-spacing; 39 | width: 0; 40 | height: 0; 41 | border-right: $s-spacing solid $brand-lightGrey; 42 | border-top: $s-spacing solid transparent; 43 | border-bottom: $s-spacing solid transparent; 44 | } 45 | 46 | .AddressBar:hover .tooltip { 47 | visibility: visible; 48 | opacity: 1; 49 | left: 100%; 50 | top: 50%; 51 | transform: translateY(-50%); 52 | margin-left: $base-spacing; 53 | z-index: 999; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/public-modules/colorado_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "colorado", 3 | "postingPlatform": "colorado", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "colorado", 7 | "networkName": "colorado.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/coverage_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "coverage", 3 | "postingPlatform": "coverage", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "coverage", 7 | "networkName": "coverage.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/local_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": { 3 | "bounties-network": ["bounties-network", "sf", "berlin"], 4 | "gitcoin": ["gitcoin"] 5 | }, 6 | "postingPlatform": "bounties-network", 7 | "postingSchema": "standardSchema", 8 | "postingSchemaVersion": "1.0", 9 | "categoryPlatform": "main", 10 | "networkName": "staging.bounties.network", 11 | "url": { 12 | "mainNet": "http://localhost:8000", 13 | "rinkeby": "http://localhost:8000" 14 | }, 15 | "requiredNetwork": "rinkeby", 16 | "contractVersion": "2.4", 17 | "mainNet": { 18 | "standardBountiesAddressV2": "0xf1c785a5b66c522aed038a82077a34d0b23799fc", 19 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 20 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 21 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 22 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 23 | }, 24 | "rinkeby": { 25 | "standardBountiesAddressV1": "0xf209d2b723b6417cbf04c07e733bee776105a073", 26 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 27 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 28 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 29 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 30 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/hocs/FormInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function FormInputHOC(WrappedComponent, type = 'redux-form') { 4 | return props => { 5 | if (type === 'formik') { 6 | const { 7 | field, // { name, value, onChange, onBlur } 8 | form: { errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc. 9 | loading 10 | } = props; 11 | 12 | return ( 13 | 18 | field.onChange({ target: { id: field.name, value: v, type: '' } }) 19 | } 20 | onBlur={v => 21 | field.onBlur({ target: { id: field.name, value: v, type: '' } }) 22 | } 23 | error={errors[field.name]} 24 | loading={loading} 25 | /> 26 | ); 27 | } 28 | 29 | // otherwise -> redux form style 30 | const { 31 | meta: { touched, error, asyncValidating }, 32 | input 33 | } = props; 34 | let componentError, 35 | componentLoading = ''; 36 | if (touched) { 37 | componentError = error; 38 | componentLoading = asyncValidating; 39 | } 40 | 41 | return ( 42 | 48 | ); 49 | }; 50 | } 51 | 52 | export default FormInputHOC; 53 | -------------------------------------------------------------------------------- /src/public-modules/ethscholars_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "ethscholars", 3 | "postingPlatform": "ethscholars", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "ethscholars", 7 | "networkName": "ethscholars.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/public-modules/surge_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "surge", 3 | "postingPlatform": "surge", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "surge", 7 | "networkName": "surge.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | }, 30 | "logo": "surge_logo" 31 | } 32 | -------------------------------------------------------------------------------- /src/public-modules/berlin_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "berlin", 3 | "postingPlatform": "berlin", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "berlin", 7 | "networkName": "berlin.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | }, 30 | "logo": "berlin_logo" 31 | } 32 | -------------------------------------------------------------------------------- /src/public-modules/boston_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "boston", 3 | "postingPlatform": "boston", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "boston", 7 | "networkName": "boston.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | }, 30 | "logo": "boston_logo" 31 | } 32 | -------------------------------------------------------------------------------- /src/public-modules/moksha_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "moksha", 3 | "postingPlatform": "moksha", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "moksha", 7 | "networkName": "moksha.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | }, 30 | "logo": "moksha_logo" 31 | } 32 | -------------------------------------------------------------------------------- /src/public-modules/test_coverage_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "coverage", 3 | "postingPlatform": "coverage", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "coverage", 7 | "networkName": "test.coverage.bounties.network", 8 | "url": { 9 | "mainNet": "https://rinkeby.api.bounties.network", 10 | "rinkeby": "https://rinkeby.api.bounties.network" 11 | }, 12 | "requiredNetwork": "rinkeby", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0xf209d2b723b6417cbf04c07e733bee776105a073", 16 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 17 | "standardBountiesAddressV2.1": "0x38F1886081759F7D352c28984908D04e8D2205A6", 18 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0xf209d2b723b6417cbf04c07e733bee776105a073", 24 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 25 | "standardBountiesAddressV2.1": "0x38F1886081759F7D352c28984908D04e8D2205A6", 26 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-typescript'], ['@babel/preset-react']], 3 | plugins: [ 4 | ['react-hot-loader/babel'], 5 | ['@babel/plugin-syntax-object-rest-spread'], 6 | ['@babel/plugin-proposal-class-properties'], 7 | ['@babel/plugin-transform-react-jsx'], 8 | [ 9 | 'import', 10 | { 11 | libraryName: 'lodash', 12 | libraryDirectory: 'fp', 13 | camel2DashComponentName: false 14 | }, 15 | 'lodash' 16 | ], 17 | [ 18 | 'import', 19 | { 20 | libraryName: 'containers', 21 | libraryDirectory: '', 22 | camel2DashComponentName: false 23 | }, 24 | 'containers' 25 | ], 26 | [ 27 | 'import', 28 | { 29 | libraryName: 'hocs', 30 | libraryDirectory: '', 31 | camel2DashComponentName: false 32 | }, 33 | 'hocs' 34 | ], 35 | [ 36 | 'import', 37 | { 38 | libraryName: 'components', 39 | libraryDirectory: '', 40 | camel2DashComponentName: false 41 | }, 42 | 'components' 43 | ], 44 | [ 45 | 'import', 46 | { 47 | libraryName: 'explorer-components', 48 | libraryDirectory: '', 49 | camel2DashComponentName: false 50 | }, 51 | 'explorer' 52 | ], 53 | [ 54 | 'import', 55 | { 56 | libraryName: 'layout', 57 | libraryDirectory: '', 58 | camel2DashComponentName: false 59 | }, 60 | 'layout' 61 | ] 62 | ] 63 | }; 64 | -------------------------------------------------------------------------------- /src/components/FullAddressBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './FullAddressBar.module.scss'; 4 | 5 | import { Text } from 'components'; 6 | 7 | const DEFAULT_TEXT = 'Copy to clipboard'; 8 | const COPIED_TEXT = 'Copied'; 9 | 10 | class FullAddressBarComponent extends React.Component { 11 | state = { 12 | tooltipText: DEFAULT_TEXT 13 | }; 14 | 15 | resetText = () => this.setState({ tooltipText: DEFAULT_TEXT }); 16 | 17 | copyToClipboard = str => { 18 | this.setState({ tooltipText: COPIED_TEXT }); 19 | const el = document.createElement('textarea'); 20 | el.value = str; 21 | document.body.appendChild(el); 22 | el.select(); 23 | document.execCommand('copy'); 24 | document.body.removeChild(el); 25 | }; 26 | 27 | render() { 28 | const { address } = this.props; 29 | 30 | return ( 31 |
    this.copyToClipboard(address)} 34 | onMouseLeave={this.resetText} 35 | > 36 | 37 | {address} 38 | 39 | {this.state.tooltipText} 40 |
    41 | ); 42 | } 43 | } 44 | 45 | FullAddressBarComponent.propTypes = { 46 | address: PropTypes.string 47 | }; 48 | 49 | FullAddressBarComponent.defaultProps = { 50 | address: '0x0000000000000000000000000000000000000000' 51 | }; 52 | 53 | export default FullAddressBarComponent; 54 | -------------------------------------------------------------------------------- /src/public-modules/waterloo_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "waterloo", 3 | "postingPlatform": "waterloo", 4 | "postingSchema": "standardSchema", 5 | "postingSchemaVersion": "1.0", 6 | "categoryPlatform": "waterloo", 7 | "networkName": "waterloo.bounties.network", 8 | "url": { 9 | "mainNet": "https://api.bounties.network", 10 | "rinkeby": "https://api.bounties.network" 11 | }, 12 | "requiredNetwork": "mainNet", 13 | "contractVersion": "2.4", 14 | "mainNet": { 15 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 16 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 17 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 18 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 19 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 20 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 21 | }, 22 | "rinkeby": { 23 | "standardBountiesAddressV1": "0x2af47a65da8CD66729b4209C22017d6A5C2d2400", 24 | "standardBountiesAddressV2": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 25 | "standardBountiesAddressV2.1": "0x43ee232734097B07803Ea605b49C6eE6Bf10f8cc", 26 | "standardBountiesAddressV2.2": "0x6e77f91ba0ae5278763ec3f044a1f0e5f85fac0a", 27 | "standardBountiesAddressV2.3": "0xa7135d0a62939501b5304a04bf00d1a9a22f6623", 28 | "standardBountiesAddressV2.4": "0x51598ae36102010feca5322098b22dd5b773428b" 29 | }, 30 | "logo": "waterloo_logo" 31 | } 32 | -------------------------------------------------------------------------------- /src/containers/Bounty/components/modals/KillBountyFormModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Modals.module.scss'; 3 | import { Modal, Button } from 'components'; 4 | import { reduxForm } from 'redux-form'; 5 | import intl from 'react-intl-universal'; 6 | 7 | const KillBountyFormModal = props => { 8 | const { onClose, handleSubmit, visible } = props; 9 | 10 | return ( 11 |
    12 | 19 | 20 | 21 | {intl.get('sections.bounty.modals.kill_bounty.title')} 22 | 23 | 24 | {intl.get('sections.bounty.modals.kill_bounty.description')} 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 |
    43 | ); 44 | }; 45 | 46 | export default reduxForm({ form: 'killBounty' })(KillBountyFormModal); 47 | -------------------------------------------------------------------------------- /src/components/Pill/Pill.module.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/variables.scss'; 2 | 3 | .pill { 4 | color: $brand-grey; 5 | border: $base-border; 6 | white-space: nowrap; 7 | padding: $s-spacing $base-spacing; 8 | display: flex; 9 | width: fit-content; 10 | height: fit-content; 11 | align-items: center; 12 | line-height: 1; 13 | 14 | &:not(:last-child) { 15 | margin-right: $s-spacing; 16 | } 17 | } 18 | 19 | .round { 20 | border-radius: 16px; 21 | } 22 | 23 | .rectangle { 24 | border-radius: $base-border-radius / 2; 25 | } 26 | 27 | .closeButton { 28 | cursor: pointer; 29 | color: $brand-blue; 30 | font-size: $body-small-font-size; 31 | margin-left: $base-spacing; 32 | 33 | &:hover { 34 | color: $brand-blue-hover; 35 | } 36 | } 37 | 38 | .purple { 39 | background-color: $brand-purple; 40 | } 41 | 42 | .blue { 43 | background-color: $brand-blue; 44 | } 45 | 46 | .orange { 47 | background-color: $brand-orange; 48 | } 49 | 50 | .green { 51 | background-color: $brand-green; 52 | } 53 | 54 | .red { 55 | background-color: $brand-red; 56 | } 57 | 58 | .nearWhite { 59 | background-color: $brand-nearWhite; 60 | } 61 | 62 | .hoverwhite { 63 | &:hover { 64 | background-color: $brand-white; 65 | } 66 | } 67 | 68 | .hovernearWhite { 69 | &:hover { 70 | cursor: pointer; 71 | background-color: $brand-nearWhite; 72 | } 73 | } 74 | 75 | .whiteBorder { 76 | border-color: $brand-white; 77 | } 78 | 79 | .lightGreyBorder { 80 | border-color: $brand-lightGrey; 81 | } 82 | 83 | .noBorder { 84 | border: none; 85 | } 86 | -------------------------------------------------------------------------------- /src/public-modules/Tokens/index.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | loading: true, 3 | loaded: false, 4 | error: false, 5 | tokens: [] 6 | }; 7 | 8 | const LOAD_TOKENS = 'tokens/LOAD_TOKENS'; 9 | const LOAD_TOKENS_SUCCESS = 'tokens/LOAD_TOKENS_SUCCESS'; 10 | const LOAD_TOKENS_FAIL = 'tokens/LOAD_TOKENS_FAIL'; 11 | 12 | function loadTokens() { 13 | return { type: LOAD_TOKENS }; 14 | } 15 | 16 | function loadTokensSuccess(tokens) { 17 | return { 18 | type: LOAD_TOKENS_SUCCESS, 19 | tokens 20 | }; 21 | } 22 | 23 | function loadTokensFail(error) { 24 | return { type: LOAD_TOKENS_FAIL, error }; 25 | } 26 | 27 | function TokensReducer(state = initialState, action) { 28 | switch (action.type) { 29 | case LOAD_TOKENS: { 30 | return { 31 | ...state, 32 | loading: true, 33 | loaded: false, 34 | error: false 35 | }; 36 | } 37 | case LOAD_TOKENS_SUCCESS: { 38 | const { tokens } = action; 39 | 40 | return { 41 | ...state, 42 | loading: false, 43 | loaded: true, 44 | tokens 45 | }; 46 | } 47 | case LOAD_TOKENS_FAIL: { 48 | return { 49 | ...state, 50 | loading: false, 51 | loaded: true, 52 | error: true 53 | }; 54 | } 55 | default: 56 | return state; 57 | } 58 | } 59 | 60 | export const actions = { 61 | loadTokens, 62 | loadTokensSuccess, 63 | loadTokensFail 64 | }; 65 | 66 | export const actionTypes = { 67 | LOAD_TOKENS, 68 | LOAD_TOKENS_SUCCESS, 69 | LOAD_TOKENS_FAIL 70 | }; 71 | 72 | export default TokensReducer; 73 | -------------------------------------------------------------------------------- /src/components/ToastContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './ToastContainer.module.scss'; 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 5 | import { Text } from 'components'; 6 | import { ToastContainer as ToastWrapper } from 'react-toastify'; 7 | import { faTimes } from '@fortawesome/pro-light-svg-icons'; 8 | 9 | const BaseToast = props => { 10 | const { icon, message, link } = props; 11 | 12 | return ( 13 |
    14 |
    15 | 16 | 17 | 18 | 19 | {message} 20 | 21 |
    22 |
    {link}
    23 |
    24 | ); 25 | }; 26 | 27 | const CloseIcon = props => { 28 | return ( 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | BaseToast.propTypes = { 36 | icon: PropTypes.array, 37 | message: PropTypes.string, 38 | link: PropTypes.node, 39 | onClose: PropTypes.func 40 | }; 41 | 42 | const ToastContainer = props => { 43 | return ( 44 | } 51 | /> 52 | ); 53 | }; 54 | 55 | ToastContainer.BaseToast = BaseToast; 56 | 57 | export default ToastContainer; 58 | -------------------------------------------------------------------------------- /src/public-modules/staging_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": { 3 | "bounties-network": ["bounties-network", "sf", "berlin"], 4 | "gitcoin": ["gitcoin"] 5 | }, 6 | "postingPlatform": "bounties-network", 7 | "postingSchema": "standardSchema", 8 | "postingSchemaVersion": "1.0", 9 | "categoryPlatform": "main", 10 | "networkName": "staging.bounties.network", 11 | "url": { 12 | "mainNet": "https://staging.api.bounties.network", 13 | "rinkeby": "https://rinkebystaging.api.bounties.network" 14 | }, 15 | "requiredNetwork": "rinkeby", 16 | "contractVersion": "2.4", 17 | "mainNet": { 18 | "standardBountiesAddress": "0xe7f69ea2a79521136ee0bf3c50f6b5f1ea0ab0cd", 19 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 20 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 21 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 22 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 23 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 24 | }, 25 | "rinkeby": { 26 | "standardBountiesAddressV1": "0xdd1636b88e9071507e859e61991ed4be6647f420", 27 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 28 | "standardBountiesAddressV2.1": "0x38F1886081759F7D352c28984908D04e8D2205A6", 29 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 30 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 31 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/public-modules/rinkeby_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": { 3 | "bounties-network": ["bounties-network", "rinkeby", "daostack"], 4 | "gitcoin": ["gitcoin"] 5 | }, 6 | "postingPlatform": "bounties-network", 7 | "postingSchema": "standardSchema", 8 | "postingSchemaVersion": "1.0", 9 | "categoryPlatform": "rinkeby", 10 | "networkName": "rinkeby.bounties.network", 11 | "url": { 12 | "mainNet": "https://rinkeby.api.bounties.network", 13 | "rinkeby": "https://rinkeby.api.bounties.network" 14 | }, 15 | "requiredNetwork": "rinkeby", 16 | "contractVersion": "2.4", 17 | "mainNet": { 18 | "standardBountiesAddressV1": "0xf209d2b723b6417cbf04c07e733bee776105a073", 19 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 20 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 21 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 22 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 23 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 24 | }, 25 | "rinkeby": { 26 | "standardBountiesAddressV1": "0xf209d2b723b6417cbf04c07e733bee776105a073", 27 | "standardBountiesAddressV2": "0xa78c680ceaa0de08ee58a28c82193fcfae22379c", 28 | "standardBountiesAddressV2.1": "0x38f1886081759f7d352c28984908d04e8d2205a6", 29 | "standardBountiesAddressV2.2": "0x9142dd986fe36952c1f8f5d68b814217dee45186", 30 | "standardBountiesAddressV2.3": "0x1ca6b906917167366324aed6c6a708131136bea9", 31 | "standardBountiesAddressV2.4": "0x6ac6baf770b3ffe2ddb3c5797e47c17cebef2ec4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Loader/Loader.module.scss: -------------------------------------------------------------------------------- 1 | @import "~styles/variables.scss"; 2 | 3 | .wrapper { 4 | position: relative; 5 | width: 32px; 6 | height: 32px; 7 | 8 | &.medium { 9 | width: 56px; 10 | height: 56px; 11 | } 12 | } 13 | 14 | .loader, 15 | .loader:after { 16 | border-radius: 50%; 17 | width: 50%; 18 | height: 50%; 19 | } 20 | 21 | .loader { 22 | margin: auto; 23 | left: 0; 24 | right: 0; 25 | top: 0; 26 | bottom: 0; 27 | position: absolute; 28 | text-indent: -9999em; 29 | border-top: 2px solid rgba($brand-white, 0.2); 30 | border-right: 2px solid rgba($brand-white, 0.2); 31 | border-bottom: 2px solid rgba($brand-white, 0.2); 32 | border-left: 2px solid $brand-white,; 33 | -webkit-transform: translateZ(0); 34 | -ms-transform: translateZ(0); 35 | transform: translateZ(0); 36 | -webkit-animation: load8 1.1s infinite linear; 37 | animation: load8 1.1s infinite linear; 38 | 39 | &.blue, 40 | &.blue:after { 41 | border-color: lighten($brand-blue, 25%); 42 | border-left-color: $brand-blue; 43 | } 44 | 45 | &.white { 46 | color: white; 47 | } 48 | 49 | &.medium { 50 | border-width: 4px; 51 | } 52 | } 53 | 54 | @-webkit-keyframes load8 { 55 | 0% { 56 | -webkit-transform: rotate(0deg); 57 | transform: rotate(0deg); 58 | } 59 | 100% { 60 | -webkit-transform: rotate(360deg); 61 | transform: rotate(360deg); 62 | } 63 | } 64 | 65 | @keyframes load8 { 66 | 0% { 67 | -webkit-transform: rotate(0deg); 68 | transform: rotate(0deg); 69 | } 70 | 100% { 71 | -webkit-transform: rotate(360deg); 72 | transform: rotate(360deg); 73 | } 74 | } 75 | --------------------------------------------------------------------------------