Various fixes (#260)

* remove linkedin social media option

* fix ProfileEdit social media button white-space

* fix default profile tab for non-authed user

* filter out non live proposal contributions for GET user/<id>

* admin: remove 255 char limit on reject_reason
This commit is contained in:
AMStrix 2019-02-25 10:41:00 -06:00 committed by William O'Beirne
parent 4c3a2127a8
commit 77e25b1c7c
9 changed files with 53 additions and 67 deletions

View File

@ -10,7 +10,6 @@ import {
Button, Button,
Collapse, Collapse,
Popconfirm, Popconfirm,
Modal,
Input, Input,
Switch, Switch,
message, message,
@ -29,14 +28,13 @@ import Back from 'components/Back';
import Info from 'components/Info'; import Info from 'components/Info';
import Markdown from 'components/Markdown'; import Markdown from 'components/Markdown';
import ArbiterControl from 'components/ArbiterControl'; import ArbiterControl from 'components/ArbiterControl';
import './index.less';
import { toZat, fromZat } from 'src/util/units'; import { toZat, fromZat } from 'src/util/units';
import FeedbackModal from '../FeedbackModal';
import './index.less';
type Props = RouteComponentProps<any>; type Props = RouteComponentProps<any>;
const STATE = { const STATE = {
showRejectModal: false,
rejectReason: '',
paidTxId: '', paidTxId: '',
}; };
@ -50,8 +48,7 @@ class ProposalDetailNaked extends React.Component<Props, State> {
} }
render() { render() {
const id = this.getIdFromQuery(); const id = this.getIdFromQuery();
const { proposalDetail: p, proposalDetailFetching, proposalDetailApproving } = store; const { proposalDetail: p, proposalDetailFetching } = store;
const { rejectReason, showRejectModal } = this.state;
if (!p || (p && p.proposalId !== id) || proposalDetailFetching) { if (!p || (p && p.proposalId !== id) || proposalDetailFetching) {
return 'loading proposal...'; return 'loading proposal...';
@ -65,7 +62,6 @@ class ProposalDetailNaked extends React.Component<Props, State> {
return m.datePaid ? prev - parseFloat(m.payoutPercent) : prev; return m.datePaid ? prev - parseFloat(m.payoutPercent) : prev;
}, 100); }, 100);
const renderDeleteControl = () => ( const renderDeleteControl = () => (
<Popconfirm <Popconfirm
onConfirm={this.handleDelete} onConfirm={this.handleDelete}
@ -81,13 +77,13 @@ class ProposalDetailNaked extends React.Component<Props, State> {
const renderCancelControl = () => ( const renderCancelControl = () => (
<Popconfirm <Popconfirm
title={( title={
<p> <p>
Are you sure you want to cancel proposal and begin Are you sure you want to cancel proposal and begin
<br /> <br />
the refund process? This cannot be undone. the refund process? This cannot be undone.
</p> </p>
)} }
placement="left" placement="left"
cancelText="cancel" cancelText="cancel"
okText="confirm" okText="confirm"
@ -181,35 +177,6 @@ class ProposalDetailNaked extends React.Component<Props, State> {
/> />
); );
const rejectModal = (
<Modal
visible={showRejectModal}
title="Reject this proposal"
onOk={this.handleReject}
onCancel={() => this.setState({ showRejectModal: false })}
okButtonProps={{
disabled: rejectReason.length === 0,
loading: proposalDetailApproving,
}}
cancelButtonProps={{
loading: proposalDetailApproving,
}}
>
Please provide a reason ({!!rejectReason.length && `${rejectReason.length}/`}
250 chars max):
<Input.TextArea
ref={ta => (this.rejectInput = ta)}
rows={4}
maxLength={250}
required={true}
value={rejectReason}
onChange={e => {
this.setState({ rejectReason: e.target.value });
}}
/>
</Modal>
);
const renderReview = () => const renderReview = () =>
p.status === PROPOSAL_STATUS.PENDING && ( p.status === PROPOSAL_STATUS.PENDING && (
<Alert <Alert
@ -232,16 +199,16 @@ class ProposalDetailNaked extends React.Component<Props, State> {
icon="close" icon="close"
type="danger" type="danger"
onClick={() => { onClick={() => {
this.setState({ showRejectModal: true }); FeedbackModal.open({
// hacky way of waiting for modal to render in before focus title: 'Reject this proposal?',
setTimeout(() => { label: 'Please provide a reason:',
if (this.rejectInput) this.rejectInput.focus(); okText: 'Reject',
}, 200); onOk: this.handleReject,
});
}} }}
> >
Reject Reject
</Button> </Button>
{rejectModal}
</div> </div>
} }
/> />
@ -357,21 +324,22 @@ class ProposalDetailNaked extends React.Component<Props, State> {
<Alert <Alert
showIcon showIcon
type="error" type="error"
message={p.stage === PROPOSAL_STAGE.FAILED ? 'Proposal failed' : 'Proposal canceled'} message={
p.stage === PROPOSAL_STAGE.FAILED ? 'Proposal failed' : 'Proposal canceled'
}
description={ description={
p.stage === PROPOSAL_STAGE.FAILED ? ( p.stage === PROPOSAL_STAGE.FAILED ? (
<> <>
This proposal failed to reach its funding goal of <b>{p.target} ZEC</b> by{' '} This proposal failed to reach its funding goal of <b>{p.target} ZEC</b> by{' '}
<b>{formatDateSeconds(p.datePublished + p.deadlineDuration)}</b>. All contributors <b>{formatDateSeconds(p.datePublished + p.deadlineDuration)}</b>. All
will need to be refunded. contributors will need to be refunded.
</> </>
) : ( ) : (
<> <>
This proposal was canceled by an admin, and will be refunding contributors This proposal was canceled by an admin, and will be refunding contributors{' '}
{' '}<b>{refundablePct}%</b> of their contributions. <b>{refundablePct}%</b> of their contributions.
</> </>
) )
} }
/> />
); );
@ -421,7 +389,6 @@ class ProposalDetailNaked extends React.Component<Props, State> {
{renderCancelControl()} {renderCancelControl()}
{renderArbiterControl()} {renderArbiterControl()}
{renderMatchingControl()} {renderMatchingControl()}
{/* TODO - other actions */}
</Card> </Card>
{/* DETAILS */} {/* DETAILS */}
@ -502,9 +469,9 @@ class ProposalDetailNaked extends React.Component<Props, State> {
store.approveProposal(true); store.approveProposal(true);
}; };
private handleReject = async () => { private handleReject = async (reason: string) => {
await store.approveProposal(false, this.state.rejectReason); await store.approveProposal(false, reason);
this.setState({ showRejectModal: false }); message.info('Proposal rejected');
}; };
private handleToggleMatching = async () => { private handleToggleMatching = async () => {

View File

@ -160,7 +160,7 @@ class ProposalContribution(db.Model):
self.status = ContributionStatus.CONFIRMED self.status = ContributionStatus.CONFIRMED
self.tx_id = tx_id self.tx_id = tx_id
self.amount = amount self.amount = amount
@hybrid_property @hybrid_property
def refund_address(self): def refund_address(self):
return self.user.settings.refund_address return self.user.settings.refund_address
@ -216,7 +216,7 @@ class Proposal(db.Model):
category = db.Column(db.String(255), nullable=False) category = db.Column(db.String(255), nullable=False)
date_approved = db.Column(db.DateTime) date_approved = db.Column(db.DateTime)
date_published = db.Column(db.DateTime) date_published = db.Column(db.DateTime)
reject_reason = db.Column(db.String(255)) reject_reason = db.Column(db.String())
# Payment info # Payment info
target = db.Column(db.String(255), nullable=False) target = db.Column(db.String(255), nullable=False)

View File

@ -83,6 +83,7 @@ def get_user(user_id, with_proposals, with_comments, with_funded, with_pending,
contributions = ProposalContribution.get_by_userid(user_id) contributions = ProposalContribution.get_by_userid(user_id)
if not authed_user or user.id != authed_user.id: if not authed_user or user.id != authed_user.id:
contributions = [c for c in contributions if c.status == ContributionStatus.CONFIRMED] contributions = [c for c in contributions if c.status == ContributionStatus.CONFIRMED]
contributions = [c for c in contributions if c.proposal.status == ProposalStatus.LIVE]
contributions_dump = user_proposal_contributions_schema.dump(contributions) contributions_dump = user_proposal_contributions_schema.dump(contributions)
result["contributions"] = contributions_dump result["contributions"] = contributions_dump
if with_comments: if with_comments:

View File

@ -0,0 +1,26 @@
"""remove linkedin social_media items
Revision ID: 332a15eba9d8
Revises: 7c7cecfe5e6c
Create Date: 2019-02-23 19:51:16.284007
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '332a15eba9d8'
down_revision = '7c7cecfe5e6c'
branch_labels = None
depends_on = None
def upgrade():
connection = op.get_bind()
connection.execute("DELETE FROM social_media WHERE service = 'LINKEDIN'")
def downgrade():
# there is no going back, all your precious linkedin profiles are gone now
pass

View File

@ -28,7 +28,7 @@ def upgrade():
sa.Column('category', sa.String(length=255), nullable=False), sa.Column('category', sa.String(length=255), nullable=False),
sa.Column('date_approved', sa.DateTime(), nullable=True), sa.Column('date_approved', sa.DateTime(), nullable=True),
sa.Column('date_published', sa.DateTime(), nullable=True), sa.Column('date_published', sa.DateTime(), nullable=True),
sa.Column('reject_reason', sa.String(length=255), nullable=True), sa.Column('reject_reason', sa.String(), nullable=True),
sa.Column('target', sa.String(length=255), nullable=False), sa.Column('target', sa.String(length=255), nullable=False),
sa.Column('payout_address', sa.String(length=255), nullable=False), sa.Column('payout_address', sa.String(length=255), nullable=False),
sa.Column('deadline_duration', sa.Integer(), nullable=False), sa.Column('deadline_duration', sa.Integer(), nullable=False),

View File

@ -164,8 +164,7 @@ class ProfileEdit extends React.PureComponent<Props, State> {
loading={loading} loading={loading}
block block
> >
{!loading && s.icon} {!loading && s.icon} <>Connect to {s.name}</>
Connect to {s.name}
</Button> </Button>
)} )}
</Form.Item> </Form.Item>

View File

@ -125,7 +125,7 @@ class Profile extends React.Component<Props, State> {
/> />
</Switch> </Switch>
<div className="Profile-tabs"> <div className="Profile-tabs">
<LinkableTabs defaultActiveKey="pending"> <LinkableTabs defaultActiveKey={(isAuthedUser && 'pending') || 'created'}>
{isAuthedUser && ( {isAuthedUser && (
<Tabs.TabPane <Tabs.TabPane
tab={TabTitle('Pending', pendingProposals.length)} tab={TabTitle('Pending', pendingProposals.length)}

View File

@ -16,12 +16,6 @@ export const SOCIAL_INFO: { [key in SOCIAL_SERVICE]: SocialInfo } = {
format: `https://twitter.com/${accountNameRegex}`, format: `https://twitter.com/${accountNameRegex}`,
icon: <Icon type="twitter" />, icon: <Icon type="twitter" />,
}, },
[SOCIAL_SERVICE.LINKEDIN]: {
service: SOCIAL_SERVICE.LINKEDIN,
name: 'LinkedIn',
format: `https://linkedin.com/in/${accountNameRegex}`,
icon: <Icon type="linkedin" />,
},
}; };
export function socialMediaToUrl(service: SOCIAL_SERVICE, username: string): string { export function socialMediaToUrl(service: SOCIAL_SERVICE, username: string): string {

View File

@ -18,5 +18,4 @@ export interface SocialInfo {
export enum SOCIAL_SERVICE { export enum SOCIAL_SERVICE {
GITHUB = 'GITHUB', GITHUB = 'GITHUB',
TWITTER = 'TWITTER', TWITTER = 'TWITTER',
LINKEDIN = 'LINKEDIN',
} }