Skip to content

Commit

Permalink
Skeleton for the contribution flow update (#4648)
Browse files Browse the repository at this point in the history
* feat(ContributionFlowUpdate): Initial layout/code/routes skeleton

* feat(ContributionFlowUpdate): General layout/code/routes update

* GraphQL schema update & lint

Co-authored-by: Benjamin Piouffle <benjamin@opencollective.com>
  • Loading branch information
sbinlondon and Betree committed Jul 27, 2020
1 parent 99c6f2a commit a34c29a
Show file tree
Hide file tree
Showing 40 changed files with 2,025 additions and 23 deletions.
2 changes: 1 addition & 1 deletion components/Avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const ContributorAvatar = ({ contributor, radius, ...styleProps }) => {
? defaultImage['ANONYMOUS']
: getCollectiveImage({ slug: contributor.collectiveSlug });

return <StyledAvatar size={radius} type={contributor.type} src={image} {...styleProps} />;
return <StyledAvatar size={radius} type={contributor.type} src={image} title={contributor.name} {...styleProps} />;
};

ContributorAvatar.propTypes = {
Expand Down
7 changes: 6 additions & 1 deletion components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ class Footer extends React.Component {
>
<Container display="flex" mt={3} width={[1, null, 1 / 3]} flexDirection="column" maxWidth="300px">
<Flex justifyContent={['center', null, 'flex-start']}>
<object type="image/svg+xml" data="/static/images/opencollectivelogo-footer.svg" height="20" />
<object
type="image/svg+xml"
data="/static/images/opencollectivelogo-footer.svg"
height="20"
style={{ maxWidth: '100%' }}
/>
</Flex>
<P textAlign={['center', null, 'left']} color="#6E747A" fontSize="1.4rem" py={2}>
An organization for your community, transparent by design.
Expand Down
6 changes: 5 additions & 1 deletion components/Steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ export default class Steps extends React.Component {
}

getStepByIndex(stepIdx) {
return stepIdx === -1 ? null : this.buildStep(this.props.steps[stepIdx], stepIdx);
return stepIdx === -1 || stepIdx >= this.props.steps.length
? null
: this.buildStep(this.props.steps[stepIdx], stepIdx);
}

getStepByName(stepName) {
Expand Down Expand Up @@ -203,6 +205,8 @@ export default class Steps extends React.Component {
const lastValidStep = this.getLastCompletedStep();
return this.props.children({
currentStep,
prevStep: this.getStepByIndex(currentStep.index - 1),
nextStep: this.getStepByIndex(currentStep.index + 1),
lastValidStep,
isValidating: this.state.isValidating,
lastVisitedStep: this.getLastVisitedStep(lastValidStep),
Expand Down
36 changes: 26 additions & 10 deletions components/StepsProgress.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { P, Span } from './Text';
const Bubble = styled(Flex)`
justify-content: center;
align-items: center;
flex: 0 0 32px;
height: 32px;
width: 32px;
border-radius: 16px;
Expand Down Expand Up @@ -63,18 +64,33 @@ const Bubble = styled(Flex)`
`}
`;

const SeparatorLine = styled(Box)`
height: 1px;
background: #e8e9eb;
/**
* Border generated with https://gigacore.github.io/demos/svg-stroke-dasharray-generator/
* to have a consistent result across browsers.
*/
const SeparatorLine = styled(props => (
<Flex alignItems="center" {...props}>
<svg width="100%" height="2" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line strokeDasharray="10%" x1="0" y1="0" x2="100%" y2="0" />
</svg>
</Flex>
))`
height: 100%;
z-index: 1;
flex-grow: 1;
flex-shrink: 1;
transition: border-color 0.3s;
line {
stroke-width: 1;
stroke: #c4c7cc;
transition: stroke 0.3s;
}
${props =>
props.active &&
css`
background: ${themeGet('colors.primary.400')};
line {
stroke: ${themeGet('colors.primary.400')};
}
`}
${props =>
Expand All @@ -90,7 +106,7 @@ const StepMobile = styled(Flex)`
`;

const StepsOuter = styled(Flex)`
padding: 16px;
padding: 12px 16px;
@media (max-width: 640px) {
background: #f5f7fa;
Expand All @@ -104,8 +120,8 @@ const StepsMobileLeft = styled(Box)`

const StepsMobileRight = styled(Flex)`
margin-left: auto;
width: 48px;
height: 48px;
width: 56px;
height: 56px;
align-items: center;
justify-content: center;
flex-shrink: 0;
Expand Down Expand Up @@ -222,14 +238,14 @@ const StepsProgress = ({
const mobileNextStep = mobileNextStepIdx !== -1 && steps[mobileNextStepIdx];
const progress = allCompleted ? 100 : (100 / steps.length) * (mobileStepIdx + 1);
const bgColor = '#D9DBDD';
const pieSize = '48';
const pieSize = '56';

return (
<StepsOuter className="steps-progress">
{viewport === VIEWPORTS.XSMALL ? (
<StepMobile>
<StepsMobileLeft>
<P color="black.900" fontWeight="600" fontSize="Paragraph">
<P color="black.900" fontWeight="500" fontSize="18px" lineHeight="26px" mb={1}>
{steps[mobileStepIdx].label || steps[mobileStepIdx].name}
</P>

Expand Down
68 changes: 68 additions & 0 deletions components/faqs/NewContributeFAQ.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';

import { Box } from '../Grid';
import StyledLink from '../StyledLink';

import FAQ, { Content, Entry, Title } from './FAQ';

/**
* FAQ associated to the new contribution flow.
*/
const ContributeAsFAQ = props => (
<FAQ withBorderLeft withNewButtons {...props}>
<Entry>
<Title>
<FormattedMessage id="NewContributionFlow.FAQ.Secure.Title" defaultMessage="Is my contribution secure?" />
</Title>
<Content>
Quam nemo umquam voluptatem appellavit, appellat; Sin te auctoritas commovebat, nobisne omnibus et Platoni ipsi
nescio quem illum anteponebas?
</Content>
</Entry>
<Entry>
<Title>
<FormattedMessage
id="createProfile.faq.persoVSOrg.title"
defaultMessage="What's the difference between a personal and an organization profile?"
/>
</Title>
<Content>
<FormattedMessage
id="createProfile.faq.persoVsOrg.content"
defaultMessage="Create an organization profile if you want to make a financial contribution in the name of your company or organization. An organization profile allows you to enable other members of your organization to make financial contributions within certain limits that you can define. Organizations can also issue gift cards."
/>
</Content>
</Entry>
<Entry>
<Title>
<FormattedMessage
id="ContributeDetails.faq.isIncognito.title"
defaultMessage="What is an incognito contribution?"
/>
</Title>
<Content>
<FormattedMessage
id="ContributeDetails.faq.isIncognito.content"
defaultMessage={
'If you chose to contribute as "incognito", your financial contribution will show up publicly as an incognito donation and it won\'t link to your public profile. However, in the effort of being transparent and compliant with KYC regulations (Know Your Customer), the fiscal host and the administrators of the collective can export a list of all the financial contributors with their personal information.'
}
/>
</Content>
</Entry>
<Box mt={2}>
<StyledLink
as={StyledLink}
href="https://www.opencollective.com"
openInNewTab
fontSize="Caption"
color="black.700"
>
<FormattedMessage id="moreInfo" defaultMessage="More info" />
&nbsp;&rarr;
</StyledLink>
</Box>
</FAQ>
);

export default ContributeAsFAQ;
48 changes: 48 additions & 0 deletions components/new-contribution-flow/ContributionFlowButtons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

import { Flex } from '../../components/Grid';
import StyledButton from '../../components/StyledButton';

class NewContributionFlowButtons extends React.Component {
static propTypes = {
goNext: PropTypes.func,
goBack: PropTypes.func,
currentStep: PropTypes.shape({ name: PropTypes.string }),
prevStep: PropTypes.shape({ name: PropTypes.string }),
nextStep: PropTypes.shape({ name: PropTypes.string }),
isRecurringContributionLoggedOut: PropTypes.bool,
};

getNextButtonLabel() {
const { currentStep, nextStep, isRecurringContributionLoggedOut } = this.props;
if (!nextStep) {
return <FormattedMessage id="contribute.submit" defaultMessage="Make contribution" />;
} else if (currentStep === 'profile' && isRecurringContributionLoggedOut) {
return <FormattedMessage id="NewContributionFlow.JoinAndGoNext" defaultMessage="Join and go next" />;
} else {
return <FormattedMessage id="contribute.nextStep" defaultMessage="Next step" />;
}
}

render() {
const { goBack, goNext } = this.props;
return (
<Flex justifyContent={'center'} mt={3}>
<Fragment>
{goBack && (
<StyledButton onClick={goBack} color="black.600">
&larr; <FormattedMessage id="Pagination.Prev" defaultMessage="Previous" />
</StyledButton>
)}
<StyledButton ml={17} buttonStyle="primary" onClick={goNext} disabled={!goNext}>
{this.getNextButtonLabel()} &rarr;
</StyledButton>
</Fragment>
</Flex>
);
}
}

export default NewContributionFlowButtons;
97 changes: 97 additions & 0 deletions components/new-contribution-flow/ContributionFlowHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl } from 'react-intl';

import Avatar, { ContributorAvatar } from '../../components/Avatar';
import Container from '../../components/Container';
import FormattedMoneyAmount from '../../components/FormattedMoneyAmount';
import { Box, Flex } from '../../components/Grid';
import { H3, P } from '../../components/Text';
import { withUser } from '../../components/UserProvider';

class NewContributionFlowHeader extends React.Component {
static propTypes = {
collective: PropTypes.shape({
currency: PropTypes.string,
name: PropTypes.string,
contributors: PropTypes.shape({
totalCount: PropTypes.number,
nodes: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
}),
),
}),
}).isRequired,
LoggedInUser: PropTypes.object,
intl: PropTypes.object,
};

render() {
const { collective } = this.props;
const contributors = collective.contributors.nodes;
const loggedInUserIsContributor = false; // TODO
const loggedInUserTotalDonations = 0; // TODO

return (
<Flex flexDirection="column" alignItems="center" maxWidth={500}>
<Avatar collective={collective} radius={65} />
<H3 textAlign="center" fontWeight={500} py={2}>
<FormattedMessage
id="CreateOrder.Title"
defaultMessage="Contribute to {collective}"
values={{ collective: collective.name }}
/>
</H3>
{loggedInUserIsContributor ? (
<P py={2}>
<FormattedMessage
id="NewContributionFlow.ContributedSoFar"
defaultMessage="You have contributed {amount} to {collective} so far. Keep it going!"
values={{
collective: collective.name,
amount: (
<FormattedMoneyAmount
precision={2}
amount={loggedInUserTotalDonations * 100}
currency={collective.currency}
amountStyles={{ fontWeight: 'bold', color: 'black.900' }}
/>
),
}}
/>
</P>
) : (
<Fragment>
{contributors?.length > 0 && (
<Fragment>
<P fontSize="LeadParagraph" lineHeight="24px" fontWeight={400} color="black.500" py={2}>
<FormattedMessage
id="NewContributionFlow.Join"
defaultMessage="Join {numberOfContributors} other fellow contributors"
values={{ numberOfContributors: collective.contributors.totalCount }}
/>
</P>
<Flex py={2} alignItems="center">
{contributors.map(contributor => (
<Box key={contributor.id} mx={1}>
<ContributorAvatar contributor={contributor} radius={24} />
</Box>
))}
{collective.contributors.totalCount > contributors.length && (
<Container fontSize="Caption" color="black.600">
+ {collective.contributors.totalCount - contributors.length}
</Container>
)}
</Flex>
</Fragment>
)}
</Fragment>
)}
</Flex>
);
}
}

export default injectIntl(withUser(NewContributionFlowHeader));
Loading

1 comment on commit a34c29a

@vercel
Copy link

@vercel vercel bot commented on a34c29a Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.