From 1ae519e251afa33a64703ce96338644ff5653d45 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Wed, 13 Mar 2019 16:39:50 -0500 Subject: [PATCH] Address TODOs (#349) * todos: simple ones, removals & modifications to NOTE * rem-todo: reduced markdown images are removed by sanitizer * be todo: add user validation to create * be todo: improve test_invide_api tests * be todo: remove todo comment * fe todo: set error messages on reducers * fe todo: upgrade and enable react-helmet * todos - remove uneeded * fe todos: remove unecessary * be: fix remaining staking contribution calculation --- admin/src/components/ProposalDetail/index.tsx | 3 - backend/grant/milestone/models.py | 1 - backend/grant/proposal/models.py | 17 +++-- backend/grant/proposal/views.py | 11 ++- backend/grant/rfp/models.py | 2 +- backend/grant/task/jobs.py | 1 - backend/grant/user/models.py | 25 +++++-- backend/tests/admin/test_admin_api.py | 32 ++++---- backend/tests/user/test_invite_api.py | 9 ++- backend/tests/user/test_user_api.py | 2 +- blockchain/src/node.ts | 73 +++++++++++-------- blockchain/src/util.ts | 20 ++--- e2e/README.md | 2 +- .../create.fund.ms2.no-vote.re-vote.spec.ts | 1 - frontend/client/components/BasicHead.tsx | 2 - frontend/client/components/Comment/index.tsx | 1 - frontend/client/components/HeaderDetails.tsx | 41 +++++------ frontend/client/components/Profile/index.tsx | 1 - .../Proposal/CampaignBlock/index.tsx | 1 - .../components/Proposal/Comments/index.tsx | 1 - frontend/client/modules/proposals/reducers.ts | 6 +- frontend/client/modules/rfps/reducers.ts | 3 +- frontend/client/utils/api.ts | 2 +- frontend/client/utils/markdown.ts | 1 - frontend/package.json | 4 +- frontend/server/components/HTML.tsx | 9 +-- frontend/types/contribution.ts | 2 +- frontend/types/user.ts | 2 +- frontend/yarn.lock | 21 ++++-- 29 files changed, 159 insertions(+), 137 deletions(-) diff --git a/admin/src/components/ProposalDetail/index.tsx b/admin/src/components/ProposalDetail/index.tsx index 2f54d3ea..65f6f4df 100644 --- a/admin/src/components/ProposalDetail/index.tsx +++ b/admin/src/components/ProposalDetail/index.tsx @@ -401,7 +401,6 @@ class ProposalDetailNaked extends React.Component { - {/* TODO - comments, milestones, updates &etc. */}
{JSON.stringify(p, null, 4)}
@@ -469,8 +468,6 @@ class ProposalDetailNaked extends React.Component { ))} - - {/* TODO: contributors here? */} diff --git a/backend/grant/milestone/models.py b/backend/grant/milestone/models.py index 9f92ae9a..16bf262c 100644 --- a/backend/grant/milestone/models.py +++ b/backend/grant/milestone/models.py @@ -22,7 +22,6 @@ class Milestone(db.Model): content = db.Column(db.Text, nullable=False) payout_percent = db.Column(db.String(255), nullable=False) immediate_payout = db.Column(db.Boolean) - # TODO: change to estimated_duration (sec or ms) -- FE can calc from dates on draft date_estimated = db.Column(db.DateTime, nullable=False) stage = db.Column(db.String(255), nullable=False) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index a23ba7c6..eb503aa4 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -376,14 +376,14 @@ class Proposal(db.Model): def get_staking_contribution(self, user_id: int): contribution = None - remaining = PROPOSAL_STAKING_AMOUNT - Decimal(self.contributed) + remaining = PROPOSAL_STAKING_AMOUNT - Decimal(self.amount_staked) # check funding if remaining > 0: # find pending contribution for any user of remaining amount - # TODO: Filter by staking=True? contribution = ProposalContribution.query.filter_by( proposal_id=self.id, status=ProposalStatus.PENDING, + staking=True, ).first() if not contribution: contribution = self.create_contribution( @@ -537,6 +537,14 @@ class Proposal(db.Model): funded = reduce(lambda prev, c: prev + Decimal(c.amount), contributions, 0) return str(funded) + @hybrid_property + def amount_staked(self): + contributions = ProposalContribution.query \ + .filter_by(proposal_id=self.id, status=ContributionStatus.CONFIRMED, staking=True) \ + .all() + amount = reduce(lambda prev, c: prev + Decimal(c.amount), contributions, 0) + return str(amount) + @hybrid_property def funded(self): target = Decimal(self.target) @@ -716,9 +724,6 @@ proposal_team_invite_schema = ProposalTeamInviteSchema() proposal_team_invites_schema = ProposalTeamInviteSchema(many=True) -# TODO: Find a way to extend ProposalTeamInviteSchema instead of redefining - - class InviteWithProposalSchema(ma.Schema): class Meta: model = ProposalTeamInvite @@ -768,7 +773,7 @@ class ProposalContributionSchema(ma.Schema): def get_addresses(self, obj): # Omit 'memo' and 'sprout' for now - # TODO: Add back in 'sapling' when ready + # NOTE: Add back in 'sapling' when ready addresses = blockchain_get('/contribution/addresses', {'contributionId': obj.id}) return { 'transparent': addresses['transparent'], diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 8dcda1bc..d4a857f3 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -4,6 +4,7 @@ from decimal import Decimal from flask import Blueprint, g, request, current_app from marshmallow import fields, validate from sqlalchemy import or_ +from sentry_sdk import capture_message from grant.extensions import limiter from grant.comment.models import Comment, comment_schema, comments_schema @@ -136,7 +137,7 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): db.session.commit() dumped_comment = comment_schema.dump(comment) - # TODO: Email proposal team if top-level comment + # Email proposal team if top-level comment if not parent: for member in proposal.team: send_email(member.email_address, 'proposal_comment', { @@ -407,7 +408,6 @@ def post_proposal_team_invite(proposal_id, address): db.session.commit() # Send email - # TODO: Move this to some background task / after request action email = address user = User.get_by_email(email_address=address) if user: @@ -531,8 +531,9 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): id=contribution_id).first() if not contribution: - # TODO: Log in sentry - current_app.logger.warn(f'Unknown contribution {contribution_id} confirmed with txid {txid}') + msg = f'Unknown contribution {contribution_id} confirmed with txid {txid}, amount {amount}' + capture_message(msg) + current_app.logger.warn(msg) return {"message": "No contribution matching id"}, 404 if contribution.status == ContributionStatus.CONFIRMED: @@ -578,8 +579,6 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): 'contributor_url': make_url(f'/profile/{contribution.user.id}') if contribution.user else '', }) - # TODO: Once we have a task queuer in place, queue emails to everyone - # on funding target reached. contribution.proposal.set_funded_when_ready() diff --git a/backend/grant/rfp/models.py b/backend/grant/rfp/models.py index c9159d43..9c29743b 100644 --- a/backend/grant/rfp/models.py +++ b/backend/grant/rfp/models.py @@ -60,7 +60,7 @@ class RFP(db.Model): matching: bool = False, status: str = RFPStatus.DRAFT, ): - # TODO add status assert + assert RFPStatus.includes(status) assert Category.includes(category) self.id = gen_random_id(RFP) self.date_created = datetime.now() diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index 690c34d2..ef2e96b2 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -30,7 +30,6 @@ class ProposalReminder: assert task.job_type == 1, "Job type: {} is incorrect for ProposalReminder".format(task.job_type) from grant.proposal.models import Proposal proposal = Proposal.query.filter_by(id=task.blob["proposal_id"]).first() - # TODO - replace with email print(proposal) task.completed = True db.session.add(task) diff --git a/backend/grant/user/models.py b/backend/grant/user/models.py index 290b90ec..f16952c3 100644 --- a/backend/grant/user/models.py +++ b/backend/grant/user/models.py @@ -1,6 +1,7 @@ from flask_security import UserMixin, RoleMixin from flask_security.core import current_user from flask_security.utils import hash_password, verify_and_update_password, login_user +from sqlalchemy.ext.hybrid import hybrid_property from grant.comment.models import Comment from grant.email.models import EmailVerification, EmailRecovery from grant.email.send import send_email @@ -10,11 +11,11 @@ from grant.email.subscription_settings import ( email_subscriptions_to_dict ) from grant.extensions import ma, db, security -from grant.utils.misc import make_url, gen_random_id +from grant.utils.misc import make_url, gen_random_id, is_email from grant.utils.social import generate_social_url from grant.utils.upload import extract_avatar_filename, construct_avatar_url from grant.utils import totp_2fa -from sqlalchemy.ext.hybrid import hybrid_property +from grant.utils.exceptions import ValidationException def is_current_authed_user_id(user_id): @@ -133,8 +134,6 @@ class User(db.Model, UserMixin): backref=db.backref('users', lazy='dynamic')) arbiter_proposals = db.relationship("ProposalArbiter", lazy=True, back_populates="user") - # TODO - add create and validate methods - def __init__( self, email_address, @@ -150,6 +149,22 @@ class User(db.Model, UserMixin): self.title = title self.password = password + @staticmethod + def validate(user): + em = user.get('email_address') + if not em: + raise ValidationException('Must have email address') + if not is_email(em): + raise ValidationException('Email address looks invalid') + + t = user.get('title') + if t and len(t) > 255: + raise ValidationException('Title is too long') + + dn = user.get('display_name') + if dn and len(dn) > 255: + raise ValidationException('Display name is too long') + @staticmethod def create(email_address=None, password=None, display_name=None, title=None, _send_email=True): user = security.datastore.create_user( @@ -158,6 +173,7 @@ class User(db.Model, UserMixin): display_name=display_name, title=title ) + User.validate(vars(user)) security.datastore.commit() # user settings @@ -249,7 +265,6 @@ class User(db.Model, UserMixin): db.session.flush() def set_admin(self, is_admin: bool): - # TODO: audit entry & possibly email user self.is_admin = is_admin db.session.add(self) db.session.flush() diff --git a/backend/tests/admin/test_admin_api.py b/backend/tests/admin/test_admin_api.py index 7360a51a..67f49f81 100644 --- a/backend/tests/admin/test_admin_api.py +++ b/backend/tests/admin/test_admin_api.py @@ -245,10 +245,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): def test_update_proposal(self): self.login_admin() # set to 1 (on) - resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 1})) + resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", + data=json.dumps({"contributionMatching": 1})) self.assert200(resp_on) self.assertEqual(resp_on.json['contributionMatching'], 1) - resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 0})) + resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", + data=json.dumps({"contributionMatching": 0})) self.assert200(resp_off) self.assertEqual(resp_off.json['contributionMatching'], 0) @@ -307,7 +309,6 @@ class TestAdminAPI(BaseProposalCreatorConfig): }) ) self.assert200(resp) - # TODO - more tests def test_create_rfp_succeeds(self): self.login_admin() @@ -315,12 +316,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): resp = self.app.post( "/api/v1/admin/rfps", data=json.dumps({ - "brief": "Some brief", - "category": "CORE_DEV", - "content": "CONTENT", - "dateCloses": 1553980004, - "status": "DRAFT", - "title": "TITLE" + "brief": "Some brief", + "category": "CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" }) ) self.assert200(resp) @@ -331,13 +332,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): resp = self.app.post( "/api/v1/admin/rfps", data=json.dumps({ - "brief": "Some brief", - "category": "NOT_CORE_DEV", - "content": "CONTENT", - "dateCloses": 1553980004, - "status": "DRAFT", - "title": "TITLE" + "brief": "Some brief", + "category": "NOT_CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" }) ) self.assert400(resp) - diff --git a/backend/tests/user/test_invite_api.py b/backend/tests/user/test_invite_api.py index 686c758e..f2927b2c 100644 --- a/backend/tests/user/test_invite_api.py +++ b/backend/tests/user/test_invite_api.py @@ -47,7 +47,10 @@ class TestUserInviteAPI(BaseProposalCreatorConfig): self.assertStatus(invites_res, 200) # Make sure we made the team, coach - self.assertTrue(len(self.other_proposal.team) == 2) # TODO: More thorough check than length + print(self.other_proposal.team) + self.assertTrue(len(self.other_proposal.team) == 2) + team_ids = [t.id for t in self.other_proposal.team] + self.assertIn(self.user.id, team_ids, 'user should be in team') def test_put_user_invite_response_reject(self): invite = ProposalTeamInvite( @@ -67,7 +70,9 @@ class TestUserInviteAPI(BaseProposalCreatorConfig): self.assertStatus(invites_res, 200) # Make sure we made the team, coach - self.assertTrue(len(self.other_proposal.team) == 1) # TODO: More thorough check than length + self.assertTrue(len(self.other_proposal.team) == 1) + team_ids = [t.id for t in self.other_proposal.team] + self.assertNotIn(self.user.id, team_ids, 'user should NOT be in team') def test_no_auth_put_user_invite_response(self): invite = ProposalTeamInvite( diff --git a/backend/tests/user/test_user_api.py b/backend/tests/user/test_user_api.py index f3cd18e7..24144ede 100644 --- a/backend/tests/user/test_user_api.py +++ b/backend/tests/user/test_user_api.py @@ -144,7 +144,7 @@ class TestUserAPI(BaseUserConfig): self.login_default_user() updated_user = animalify(copy.deepcopy(user_schema.dump(self.user))) updated_user["displayName"] = 'new display name' - updated_user["avatar"] = '' # TODO confirm avatar is no longer a dict + updated_user["avatar"] = '' updated_user["socialMedias"] = [] user_update_resp = self.app.put( diff --git a/blockchain/src/node.ts b/blockchain/src/node.ts index 4ef43be2..c7638ffb 100644 --- a/blockchain/src/node.ts +++ b/blockchain/src/node.ts @@ -33,7 +33,6 @@ export interface VOut { scriptPubKey: ScriptPubKey; } - export interface Transaction { txid: string; hex: string; @@ -46,7 +45,7 @@ export interface Transaction { time: number; vin: VIn[]; vout: VOut[]; - // TODO: fill me out, what is this? + // unclear what vjoinsplit is vjoinsplit: any[]; } @@ -74,7 +73,6 @@ export interface BlockWithTransactionIds extends Block { tx: string[]; } - export interface BlockWithTransactions extends Block { tx: Transaction[]; } @@ -107,33 +105,44 @@ export interface ValidationResponse { isvalid: boolean; } - -// TODO: Type all methods with signatures from // https://github.com/zcash/zcash/blob/master/doc/payment-api.md interface ZCashNode { getblockchaininfo: () => Promise; getblockcount: () => Promise; getblock: { - (numberOrHash: string | number, verbosity?: 1): Promise; - (numberOrHash: string | number, verbosity: 2): Promise; + (numberOrHash: string | number, verbosity?: 1): Promise< + BlockWithTransactionIds + >; + (numberOrHash: string | number, verbosity: 2): Promise< + BlockWithTransactions + >; (numberOrHash: string | number, verbosity: 0): Promise; - } + }; gettransaction: (txid: string) => Promise; validateaddress: (address: string) => Promise; z_getbalance: (address: string, minConf?: number) => Promise; - z_getnewaddress: (type?: 'sprout' | 'sapling') => Promise; + z_getnewaddress: (type?: "sprout" | "sapling") => Promise; z_listaddresses: () => Promise; - z_listreceivedbyaddress: (address: string, minConf?: number) => Promise; - z_importviewingkey: (key: string, rescan?: 'yes' | 'no' | 'whenkeyisnew', startHeight?: number) => Promise; + z_listreceivedbyaddress: ( + address: string, + minConf?: number + ) => Promise; + z_importviewingkey: ( + key: string, + rescan?: "yes" | "no" | "whenkeyisnew", + startHeight?: number + ) => Promise; z_exportviewingkey: (zaddr: string) => Promise; - z_validatepaymentdisclosure: (disclosure: string) => Promise; + z_validatepaymentdisclosure: ( + disclosure: string + ) => Promise; z_validateaddress: (address: string) => Promise; } export const rpcOptions = { url: env.ZCASH_NODE_URL, username: env.ZCASH_NODE_USERNAME, - password: env.ZCASH_NODE_PASSWORD, + password: env.ZCASH_NODE_PASSWORD }; const node: ZCashNode = stdrpc(rpcOptions); @@ -152,28 +161,31 @@ export async function initNode() { } if (info.chain.includes("test")) { network = bitcore.Networks.testnet; - } - else { + } else { network = bitcore.Networks.mainnet; } - } - catch(err) { + } catch (err) { captureException(err); log.error(err.response ? err.response.data : err); - log.error('Failed to connect to zcash node with the following credentials:\r\n', rpcOptions); + log.error( + "Failed to connect to zcash node with the following credentials:\r\n", + rpcOptions + ); process.exit(1); } // Check if sprout address is readable try { if (!env.SPROUT_ADDRESS) { - console.error('Missing SPROUT_ADDRESS environment variable, exiting'); + console.error("Missing SPROUT_ADDRESS environment variable, exiting"); process.exit(1); } await node.z_getbalance(env.SPROUT_ADDRESS as string); - } catch(err) { + } catch (err) { if (!env.SPROUT_VIEWKEY) { - log.error('Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting'); + log.error( + "Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting" + ); process.exit(1); } await node.z_importviewingkey(env.SPROUT_VIEWKEY as string); @@ -183,7 +195,7 @@ export async function initNode() { export function getNetwork() { if (!network) { - throw new Error('Called getNetwork before initNode'); + throw new Error("Called getNetwork before initNode"); } return network; } @@ -194,11 +206,15 @@ export async function getBootstrapBlockHeight(txid: string | undefined) { try { const tx = await node.gettransaction(txid); const block = await node.getblock(tx.blockhash); - const height = block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); + const height = + block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); return height.toString(); - } catch(err) { - console.warn(`Attempted to get block height for tx ${txid} but failed with the following error:\n`, err); - console.warn('Falling back to hard-coded starter blocks'); + } catch (err) { + console.warn( + `Attempted to get block height for tx ${txid} but failed with the following error:\n`, + err + ); + console.warn("Falling back to hard-coded starter blocks"); } } @@ -207,11 +223,10 @@ export async function getBootstrapBlockHeight(txid: string | undefined) { const net = getNetwork(); if (net === bitcore.Networks.mainnet) { return env.MAINNET_START_BLOCK; - } - else if (net === bitcore.Networks.testnet && !net.regtestEnabled) { + } else if (net === bitcore.Networks.testnet && !net.regtestEnabled) { return env.TESTNET_START_BLOCK; } // Regtest or otherwise unknown networks should start at the bottom - return '0'; + return "0"; } diff --git a/blockchain/src/util.ts b/blockchain/src/util.ts index 94438566..f15e1d7f 100755 --- a/blockchain/src/util.ts +++ b/blockchain/src/util.ts @@ -21,8 +21,7 @@ export function authenticate(secret: string) { return hash === sha256(secret); } -// TODO: Not fully confident in compatibility with most bip32 wallets, -// do more work to ensure this is reliable. +// NOTE: this is just one way to derive t-addrs export function deriveTransparentAddress(index: number, network: any) { const root = new HDPublicKey(env.BIP32_XPUB); const child = root.derive(`m/0/${index}`); @@ -39,14 +38,16 @@ export function removeItem(arr: T[], remove: T) { } export function encodeHexMemo(memo: string) { - return new Buffer(memo, 'utf8').toString('hex'); + return new Buffer(memo, "utf8").toString("hex"); } export function decodeHexMemo(memoHex: string) { - return new Buffer(memoHex, 'hex') - .toString() - // Remove null bytes from zero padding - .replace(/\0.*$/g, ''); + return ( + new Buffer(memoHex, "hex") + .toString() + // Remove null bytes from zero padding + .replace(/\0.*$/g, "") + ); } export function makeContributionMemo(contributionId: number) { @@ -54,14 +55,15 @@ export function makeContributionMemo(contributionId: number) { } export function getContributionIdFromMemo(memoHex: string) { - const matches = decodeHexMemo(memoHex).match(/Contribution ([0-9]+) on Grant\.io/); + const matches = decodeHexMemo(memoHex).match( + /Contribution ([0-9]+) on Grant\.io/ + ); if (matches && matches[1]) { return parseInt(matches[1], 10); } return false; } -// TODO: Make this more robust export function toBaseUnit(unit: number) { return Math.floor(100000000 * unit); } diff --git a/e2e/README.md b/e2e/README.md index e48d83b1..08c57b52 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -21,4 +21,4 @@ Tests can be found in `cypress/integration`. Cypress will hot-reload open tests ### CI -TODO +Coming soon. diff --git a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts b/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts index c361e1c5..eb1f7a65 100644 --- a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts +++ b/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts @@ -79,7 +79,6 @@ describe("create.fund.ms2.no-vote.re-vote", () => { "Request milestone payout", { timeout: 20000 } ).click(); - // TODO: fix this bug (the following fails) cy.contains(".MilestoneAction-progress-text", "voted against payout", { timeout: 20000 }); diff --git a/frontend/client/components/BasicHead.tsx b/frontend/client/components/BasicHead.tsx index 50424f19..29895da4 100644 --- a/frontend/client/components/BasicHead.tsx +++ b/frontend/client/components/BasicHead.tsx @@ -25,7 +25,6 @@ class BasicHead extends React.Component { name="keywords" content="Zcash, Zcash Foundation, Zcash Foundation Grants, Zcash Grants, Zcash Grant, ZF Grants, ZFGrants" /> - { - {/* TODO: i18n */} {/* */} {/* */} diff --git a/frontend/client/components/Comment/index.tsx b/frontend/client/components/Comment/index.tsx index 2cd80ab0..a2ada52f 100644 --- a/frontend/client/components/Comment/index.tsx +++ b/frontend/client/components/Comment/index.tsx @@ -41,7 +41,6 @@ class Comment extends React.Component { }; componentDidUpdate(prevProps: Props) { - // TODO: Come up with better check on if our comment post was a success const { isPostCommentPending, postCommentError } = this.props; if (!isPostCommentPending && !postCommentError && prevProps.isPostCommentPending) { this.setState({ reply: '', isReplying: false }); diff --git a/frontend/client/components/HeaderDetails.tsx b/frontend/client/components/HeaderDetails.tsx index a4229d59..5d0d6214 100644 --- a/frontend/client/components/HeaderDetails.tsx +++ b/frontend/client/components/HeaderDetails.tsx @@ -1,6 +1,6 @@ import React from 'react'; -// import { Helmet } from 'react-helmet'; -// import { urlToPublic } from 'utils/helpers'; +import { Helmet } from 'react-helmet'; +import { urlToPublic } from 'utils/helpers'; interface Props { title: string; @@ -12,25 +12,22 @@ interface Props { export default class HeaderDetails extends React.Component { render() { - // TODO: Uncomment once helmet is fixed - // https://github.com/nfl/react-helmet/issues/373 - return null; - // const { title, image, url, type, description } = this.props; - // return ( - // - // {`ZF Grants - ${title}`} - // {/* open graph protocol */} - // {type && } - // - // {description && } - // {url && } - // {image && } - // {/* twitter card */} - // - // {description && } - // {url && } - // {image && } - // - // ); + const { title, image, url, type, description } = this.props; + return ( + + {`ZF Grants - ${title}`} + {/* open graph protocol */} + {type && } + + {description && } + {url && } + {image && } + {/* twitter card */} + + {description && } + {url && } + {image && } + + ); } } diff --git a/frontend/client/components/Profile/index.tsx b/frontend/client/components/Profile/index.tsx index 8c9a51e0..43256ef6 100644 --- a/frontend/client/components/Profile/index.tsx +++ b/frontend/client/components/Profile/index.tsx @@ -106,7 +106,6 @@ class Profile extends React.Component { return (
- {/* TODO: customize details for funders/creators */} { return; } - // TODO: Get values from proposal const { target, funded } = this.props.proposal; const remainingTarget = target.sub(funded); const amount = parseFloat(value); diff --git a/frontend/client/components/Proposal/Comments/index.tsx b/frontend/client/components/Proposal/Comments/index.tsx index 4a7fba18..54428f6d 100644 --- a/frontend/client/components/Proposal/Comments/index.tsx +++ b/frontend/client/components/Proposal/Comments/index.tsx @@ -55,7 +55,6 @@ class ProposalComments extends React.Component { } componentDidUpdate(prevProps: Props) { - // TODO: Come up with better check on if our comment post was a success const { isPostCommentPending, postCommentError } = this.props; if (!isPostCommentPending && !postCommentError && prevProps.isPostCommentPending) { this.setState({ comment: '' }); diff --git a/frontend/client/modules/proposals/reducers.ts b/frontend/client/modules/proposals/reducers.ts index 05b2330f..08efcb67 100644 --- a/frontend/client/modules/proposals/reducers.ts +++ b/frontend/client/modules/proposals/reducers.ts @@ -386,8 +386,7 @@ export default (state = INITIAL_STATE, action: any) => { case types.PROPOSAL_UPDATES_REJECTED: return { ...state, - // TODO: Get action to send real error - updatesError: 'Failed to fetch updates', + updatesError: (payload && payload.message) || payload.toString(), isFetchingUpdates: false, }; @@ -402,8 +401,7 @@ export default (state = INITIAL_STATE, action: any) => { case types.PROPOSAL_CONTRIBUTIONS_REJECTED: return { ...state, - // TODO: Get action to send real error - fetchContributionsError: 'Failed to fetch updates', + fetchContributionsError: (payload && payload.message) || payload.toString(), isFetchingContributions: false, }; diff --git a/frontend/client/modules/rfps/reducers.ts b/frontend/client/modules/rfps/reducers.ts index 9bed264d..609cc289 100644 --- a/frontend/client/modules/rfps/reducers.ts +++ b/frontend/client/modules/rfps/reducers.ts @@ -47,8 +47,7 @@ export default (state: RFPState = INITIAL_STATE, action: any): RFPState => { case types.FETCH_RFP_REJECTED: return { ...state, - // TODO: Get action to send real error - fetchRfpsError: 'Failed to fetch rfps', + fetchRfpsError: (payload && payload.message) || payload.toString(), isFetchingRfps: false, }; case types.FETCH_RFPS_FULFILLED: diff --git a/frontend/client/utils/api.ts b/frontend/client/utils/api.ts index 1d0b3e33..98fb3fb5 100644 --- a/frontend/client/utils/api.ts +++ b/frontend/client/utils/api.ts @@ -114,7 +114,7 @@ export function formatRFPFromGet(rfp: RFP): RFP { return rfp; } -// TODO: i18n on case-by-case basis +// NOTE: i18n on case-by-case basis export function generateSlugUrl(id: number, title: string) { const slug = title .toLowerCase() diff --git a/frontend/client/utils/markdown.ts b/frontend/client/utils/markdown.ts index 8613d0c0..2feed465 100644 --- a/frontend/client/utils/markdown.ts +++ b/frontend/client/utils/markdown.ts @@ -27,7 +27,6 @@ const converters: { [key in MARKDOWN_TYPE]: Showdown.Converter } = { ...sharedOptions, noHeaderId: true, headerLevelStart: 4, - // TODO: Find a way to disable images }), }; diff --git a/frontend/package.json b/frontend/package.json index af5c11d9..3b993abe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,7 +60,7 @@ "@types/react": "16.4.18", "@types/react-cropper": "^0.10.3", "@types/react-dom": "16.0.9", - "@types/react-helmet": "^5.0.7", + "@types/react-helmet": "^5.0.8", "@types/react-redux": "^6.0.2", "@types/react-router": "4.4.3", "@types/react-router-dom": "^4.3.1", @@ -126,7 +126,7 @@ "react-cropper": "^1.0.1", "react-dev-utils": "^5.0.2", "react-dom": "16.5.2", - "react-helmet": "^5.2.0", + "react-helmet": "6.0.0-beta", "react-hot-loader": "^4.3.8", "react-i18next": "^8.3.5", "react-mde": "7.0.4", diff --git a/frontend/server/components/HTML.tsx b/frontend/server/components/HTML.tsx index 416c9383..ede71704 100644 --- a/frontend/server/components/HTML.tsx +++ b/frontend/server/components/HTML.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import Helmet from 'react-helmet'; +import { Helmet } from 'react-helmet'; import { ChunkExtractor } from '@loadable/server'; export interface Props { @@ -34,13 +34,6 @@ const HTML: React.SFC = ({ - {/* TODO: import from @fortawesome */} - {/* */} {/* Custom link & meta tags from webpack */} {extractor.getLinkElements()} {linkTags.map((l, idx) => ( diff --git a/frontend/types/contribution.ts b/frontend/types/contribution.ts index e72b8a2f..4a8bbae0 100644 --- a/frontend/types/contribution.ts +++ b/frontend/types/contribution.ts @@ -13,7 +13,7 @@ export interface Contribution { export interface ContributionWithAddresses extends Contribution { addresses: { transparent: string; - // TODO: Add sapling and memo in when ready + // NOTE: Add sapling and memo in when ready // sprout: string; // memo: string; }; diff --git a/frontend/types/user.ts b/frontend/types/user.ts index 162b901a..85a122f0 100644 --- a/frontend/types/user.ts +++ b/frontend/types/user.ts @@ -2,7 +2,7 @@ import { SocialMedia } from 'types'; export interface User { userid: number; - emailAddress?: string; // TODO: Split into full user type + emailAddress?: string; emailVerified?: boolean; displayName: string; title: string; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fc0380f4..c2737825 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1940,9 +1940,10 @@ "@types/node" "*" "@types/react" "*" -"@types/react-helmet@^5.0.7": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.7.tgz#1cae65b2c37fe54cf56f40cd388836d4619dbc51" +"@types/react-helmet@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.8.tgz#f080eea6652e44f60b4574463d238f268d81d9af" + integrity sha512-ZTr12eDAYI0yUiMx1K82EHqRYa8J1BOOLus+0gL+AkksUiIPwLE0wLiXa9FNqD8r9GXAi+yRPZImkRh1JNlTkQ== dependencies: "@types/react" "*" @@ -9601,6 +9602,11 @@ react-error-overlay@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" +react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-fittext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-fittext/-/react-fittext-1.0.0.tgz#836a1c04f9322f6c94cb69e45c66006fc42d37a5" @@ -9617,13 +9623,14 @@ react-fuzzy@^0.5.2: fuse.js "^3.0.1" prop-types "^15.5.9" -react-helmet@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7" +react-helmet@6.0.0-beta: + version "6.0.0-beta" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.0.0-beta.tgz#1f2ac04521951486e4fce3296d0c88aae8cabd5c" + integrity sha512-GnNWsokebTe7fe8MH2I/a2dl4THYWhthLBoMaQSRYqW5XbPo881WAJGi+lqRBjyOFryW6zpQluEkBy70zh+h9w== dependencies: - deep-equal "^1.0.1" object-assign "^4.1.1" prop-types "^15.5.4" + react-fast-compare "^2.0.2" react-side-effect "^1.1.0" react-hot-loader@^4.3.8: