diff options
Diffstat (limited to 'packages/website/ts/pages')
33 files changed, 3233 insertions, 1993 deletions
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx deleted file mode 100644 index dfe8926b0..000000000 --- a/packages/website/ts/pages/about/about.tsx +++ /dev/null @@ -1,413 +0,0 @@ -import { colors, Link, Styles } from '@0x/react-shared'; -import * as _ from 'lodash'; -import * as React from 'react'; -import * as DocumentTitle from 'react-document-title'; -import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar/top_bar'; -import { Profile } from 'ts/pages/about/profile'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProfileInfo, WebsitePaths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; -import { utils } from 'ts/utils/utils'; - -const teamRow1: ProfileInfo[] = [ - { - name: 'Will Warren', - title: 'Co-founder & CEO', - description: `Smart contract R&D. Previously applied physics at Los Alamos \ - Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`, - image: '/images/team/will.jpg', - linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', - github: 'https://github.com/willwarren89', - medium: 'https://medium.com/@willwarren89', - }, - { - name: 'Amir Bandeali', - title: 'Co-founder & CTO', - description: `Smart contract R&D. Previously fixed income trader at DRW. \ - Finance at University of Illinois, Urbana-Champaign.`, - image: '/images/team/amir.png', - linkedIn: 'https://www.linkedin.com/in/abandeali1/', - github: 'https://github.com/abandeali1', - medium: 'https://medium.com/@abandeali1', - }, - { - name: 'Fabio Berger', - title: 'Senior Engineer', - description: `Full-stack blockchain engineer. Previously software engineer \ - at Airtable and founder of WealthLift. Computer Science at Duke.`, - image: '/images/team/fabio.jpg', - linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', - github: 'https://github.com/fabioberger', - medium: 'https://medium.com/@fabioberger', - }, -]; - -const teamRow2: ProfileInfo[] = [ - { - name: 'Alex Xu', - title: 'Director of Operations', - description: `Strategy and operations. Previously digital marketing at Google \ - and vendor management at Amazon. Economics at UC San Diego.`, - image: '/images/team/alex.jpg', - linkedIn: 'https://www.linkedin.com/in/alex-xu/', - github: '', - medium: 'https://medium.com/@aqxu', - }, - { - name: 'Leonid Logvinov', - title: 'Engineer', - description: `Full-stack blockchain engineer. Previously blockchain engineer \ - at Neufund. Computer Science at University of Warsaw.`, - image: '/images/team/leonid.png', - linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', - github: 'https://github.com/LogvinovLeon', - medium: 'https://medium.com/@Logvinov', - }, - { - name: 'Ben Burns', - title: 'Designer', - description: `Product, motion, and graphic designer. Previously designer \ - at Airtable and Apple. Digital Design at University of Cincinnati.`, - image: '/images/team/ben.jpg', - linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', - github: '', - medium: '', - }, -]; - -const teamRow3: ProfileInfo[] = [ - { - name: 'Brandon Millman', - title: 'Senior Engineer', - description: `Full-stack engineer. Previously senior software engineer at \ - Twitter. Computer Science and Electrical Engineering at Duke.`, - image: '/images/team/brandon.png', - linkedIn: 'https://www.linkedin.com/in/brandon-millman-b093a022/', - github: 'https://github.com/BMillman19', - medium: 'https://medium.com/@bchillman', - }, - { - name: 'Tom Schmidt', - title: 'Product Manager', - description: `Previously engineering at Apple, product management at Facebook and Instagram. Computer Science at Stanford.`, - image: '/images/team/tom.jpg', - linkedIn: 'https://www.linkedin.com/in/tomhschmidt/', - github: 'https://github.com/tomhschmidt', - medium: '', - }, - { - name: 'Jacob Evans', - title: 'Ecosystem Engineer', - description: `Previously software engineer at Qantas and RSA Security.`, - image: '/images/team/jacob.jpg', - linkedIn: 'https://www.linkedin.com/in/dekzter/', - github: 'https://github.com/dekz', - medium: '', - }, -]; - -const teamRow4: ProfileInfo[] = [ - { - name: 'Blake Henderson', - title: 'Operations Associate', - description: `Operations and Analytics. Previously analytics at LinkedIn. Economics at UC San Diego.`, - image: '/images/team/blake.jpg', - linkedIn: 'https://www.linkedin.com/in/blakerhenderson/', - github: '', - medium: '', - }, - { - name: 'Zack Skelly', - title: 'Lead Recruiter', - description: `Talent. Previously first recruiter at Heap, recruiting at Dropbox and Google. English Rhetoric and Composition at Pepperdine.`, - image: '/images/team/zach.png', - linkedIn: 'https://www.linkedin.com/in/zackaryskelly/', - github: '', - medium: '', - }, - { - name: 'Greg Hysen', - title: 'Blockchain Engineer', - description: `Smart contract R&D. Previously lead distributed systems engineer at Hivemapper. Computer Science at University of Waterloo.`, - image: '/images/team/greg.jpeg', - linkedIn: 'https://www.linkedin.com/in/gregory-hysen-71741463/', - github: 'https://github.com/hysz', - medium: '', - }, -]; - -const teamRow5: ProfileInfo[] = [ - { - name: 'Remco Bloemen', - title: 'Technical Fellow', - description: `Previously cofounder at Neufund and Coblue. Part III at Cambridge. PhD dropout at Twente Business School.`, - image: '/images/team/remco.jpeg', - linkedIn: 'https://www.linkedin.com/in/remcobloemen/', - github: 'http://github.com/recmo', - medium: '', - }, - { - name: 'Francesco Agosti', - title: 'Engineer', - description: `Full-stack engineer. Previously senior software engineer at Yelp. Computer Science at Duke.`, - image: 'images/team/fragosti.png', - linkedIn: 'https://www.linkedin.com/in/fragosti/', - github: 'http://github.com/fragosti', - }, - { - name: 'Mel Oberto', - title: 'Office Ops / Executive Assistant', - description: `Daily Operations. Previously People Operations Associate at Heap. Marketing and MBA at Sacred Heart University.`, - image: 'images/team/mel.png', - linkedIn: 'https://www.linkedin.com/in/melanieoberto', - }, -]; - -const teamRow6: ProfileInfo[] = [ - { - name: 'Alex Browne', - title: 'Engineer in Residence', - description: `Full-stack blockchain engineer. Previously at Plaid. Open source guru and footgun dismantler. Computer Science and Electrical Engineering at Duke.`, - image: 'images/team/alexbrowne.png', - linkedIn: 'https://www.linkedin.com/in/stephenalexbrowne/', - github: 'http://github.com/albrow', - }, - { - name: 'Peter Zeitz', - title: 'Research Fellow', - description: `Researching decentralized governance. Previously Assistant Professor of Economics at National University of Singapore Business School. PhD in Economics at UCLA.`, - image: 'images/team/peter.jpg', - linkedIn: 'https://www.linkedin.com/in/peter-z-7b9595163/', - }, - { - name: 'Chris Kalani', - title: 'Director of Design', - description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`, - image: 'images/team/chris.png', - linkedIn: 'https://www.linkedin.com/in/chriskalani/', - github: 'https://github.com/chriskalani', - }, -]; - -const teamRow7: ProfileInfo[] = [ - { - name: 'Clay Robbins', - title: 'Ecosystem Development Lead', - description: `Growth & Business Development. Previously product and partnerships at Square. Economics at Dartmouth College.`, - image: 'images/team/clay.png', - linkedIn: 'https://www.linkedin.com/in/robbinsclay/', - }, - { - name: 'Matt Taylor', - title: 'Marketing Lead', - description: `Growth & Marketing. Previously marketing at Abra and Square. Economics and Philosophy at Claremont McKenna College.`, - image: 'images/team/matt.jpg', - linkedIn: 'https://www.linkedin.com/in/mattytay/', - }, - { - name: 'Eugene Aumson', - title: 'Engineer', - description: `Developer Experience. Previously senior software engineer in foreign exchange applications at Bloomberg LP.`, - image: 'images/team/gene.jpg', - linkedIn: 'https://www.linkedin.com/in/aumson/', - github: 'https://github.com/feuGeneA', - }, -]; - -const teamRow8: ProfileInfo[] = [ - { - name: 'Weijie Wu', - title: 'Research Fellow', - description: `Researching decentralized governance. Previously Researcher at Huawei and Assistant Professor at Shanghai Jiao Tong University. PhD in Computer Science at The Chinese University of Hong Kong.`, - image: 'images/team/weijie.png', - linkedIn: 'https://www.linkedin.com/in/weijiewu/', - }, - { - name: 'Rahul Singireddy', - title: 'Relayer Success Manager', - description: `Previously community at Zeppelin, growth at Dharma, and cryptocurrency contributor at Forbes. Symbolic Systems at Stanford.`, - image: 'images/team/rahul.png', - linkedIn: 'https://www.linkedin.com/in/rahul-singireddy-3037908a/', - }, - { - name: 'Jason Somensatto', - title: 'Strategic Legal Counsel', - description: `Legal. Previously head of blockchain and crypto practice at Orrick. JD from George Washington University and undergrad at UVA.`, - image: 'images/team/jason.png', - linkedIn: 'https://www.linkedin.com/in/jasonsomensatto/', - }, -]; - -const teamRow9: ProfileInfo[] = [ - { - name: 'Steve Klebanoff', - title: 'Senior Engineer', - description: ` Full-stack engineer. Previously Staff Software Engineer at AppFolio. Computer Science & Cognitive Psychology at Northeastern University.`, - image: 'images/team/steve.png', - linkedIn: 'https://www.linkedin.com/in/steveklebanoff/', - github: 'https://github.com/steveklebanoff', - }, -]; - -const advisors1: ProfileInfo[] = [ - { - name: 'Fred Ehrsam', - description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', - image: '/images/advisors/fred.jpg', - linkedIn: 'https://www.linkedin.com/in/fredehrsam/', - medium: 'https://medium.com/@FEhrsam', - twitter: 'https://twitter.com/FEhrsam', - }, - { - name: 'Olaf Carlson-Wee', - image: '/images/advisors/olaf.png', - description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', - linkedIn: 'https://www.linkedin.com/in/olafcw/', - angellist: 'https://angel.co/olafcw', - }, - { - name: 'Joey Krug', - description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, - image: '/images/advisors/joey.jpg', - linkedIn: 'https://www.linkedin.com/in/joeykrug/', - github: 'https://github.com/joeykrug', - angellist: 'https://angel.co/joeykrug', - }, -]; - -const advisors2: ProfileInfo[] = [ - { - name: 'Linda Xie', - description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', - image: '/images/advisors/linda.jpg', - linkedIn: 'https://www.linkedin.com/in/lindaxie/', - medium: 'https://medium.com/@linda.xie', - twitter: 'https://twitter.com/ljxie', - }, - { - name: 'David Sacks', - description: 'General Partner at Craft Ventures. Original COO of PayPal. Founder of Yammer.', - image: '/images/advisors/david.png', - linkedIn: 'https://www.linkedin.com/in/davidoliversacks/', - medium: 'https://medium.com/@davidsacks', - twitter: 'https://twitter.com/DavidSacks', - }, -]; - -export interface AboutProps { - source: string; - location: Location; - translate: Translate; - dispatcher: Dispatcher; -} - -interface AboutState {} - -const styles: Styles = { - header: { - fontFamily: 'Roboto Mono', - fontSize: 36, - color: 'black', - paddingTop: 110, - }, - weAreHiring: { - fontSize: 30, - color: colors.darkestGrey, - fontFamily: 'Roboto Mono', - letterSpacing: 7.5, - }, -}; - -export class About extends React.Component<AboutProps, AboutState> { - public componentDidMount(): void { - window.scrollTo(0, 0); - } - public render(): React.ReactNode { - return ( - <div style={{ backgroundColor: colors.lightestGrey }}> - <DocumentTitle title="0x About Us" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - style={{ backgroundColor: colors.lightestGrey }} - translate={this.props.translate} - /> - <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> - <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> - <div style={styles.header}>About us:</div> - <div - className="pt3" - style={{ - fontSize: 17, - color: colors.darkestGrey, - lineHeight: 1.5, - }} - > - Our team is a globally distributed group with backgrounds in engineering, research, business - and design. We are passionate about decentralized technology and its potential to act as an - equalizing force in the world. - </div> - </div> - <div className="pt3 md-px4 lg-px0"> - <div className="clearfix pb3">{this._renderProfiles(teamRow1)}</div> - <div className="clearfix">{this._renderProfiles(teamRow2)}</div> - <div className="clearfix">{this._renderProfiles(teamRow3)}</div> - <div className="clearfix">{this._renderProfiles(teamRow4)}</div> - <div className="clearfix">{this._renderProfiles(teamRow5)}</div> - <div className="clearfix">{this._renderProfiles(teamRow6)}</div> - <div className="clearfix">{this._renderProfiles(teamRow7)}</div> - <div className="clearfix">{this._renderProfiles(teamRow8)}</div> - <div className="clearfix">{this._renderProfiles(teamRow9)}</div> - </div> - <div className="pt3 pb2"> - <div - className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3" - style={{ - color: colors.grey, - fontSize: 24, - fontFamily: 'Roboto Mono', - }} - > - Advisors: - </div> - <div className="clearfix">{this._renderProfiles(advisors1)}</div> - <div className="clearfix">{this._renderProfiles(advisors2)}</div> - </div> - <div className="mx-auto py4 sm-px3" style={{ maxWidth: 308 }}> - <div className="pb2" style={styles.weAreHiring}> - WE'RE HIRING - </div> - <div - className="pb4 mb4" - style={{ - fontSize: 16, - color: colors.darkestGrey, - lineHeight: 1.5, - letterSpacing: '0.5px', - }} - > - We are seeking outstanding candidates to{' '} - <Link to={WebsitePaths.Careers} textDecoration="underline" fontColor="black"> - join our team - </Link> - . We value passion, diversity and unique perspectives. - </div> - </div> - </div> - <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> - </div> - ); - } - private _renderProfiles(profiles: ProfileInfo[]): React.ReactNode { - const numIndiv = profiles.length; - const colSize = utils.getColSize(numIndiv); - return _.map(profiles, profile => { - return ( - <div key={`profile-${profile.name}`}> - <Profile colSize={colSize} profileInfo={profile} /> - </div> - ); - }); - } -} diff --git a/packages/website/ts/pages/about/jobs.tsx b/packages/website/ts/pages/about/jobs.tsx new file mode 100644 index 000000000..ee1aa6cef --- /dev/null +++ b/packages/website/ts/pages/about/jobs.tsx @@ -0,0 +1,236 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled from 'styled-components'; + +import { AboutPageLayout } from 'ts/components/aboutPageLayout'; +import { Link } from 'ts/components/link'; +import { Column, FlexWrap, Section } from 'ts/components/newLayout'; +import { Heading, Paragraph } from 'ts/components/text'; +import { Container } from 'ts/components/ui/container'; +import { colors } from 'ts/style/colors'; +import { WebsiteBackendJobInfo } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; +import { constants } from 'ts/utils/constants'; + +const OPEN_POSITIONS_HASH = 'positions'; + +interface PositionProps { + title: string; + location: string; + href: string; +} + +interface PositionItemProps { + position: PositionProps; +} + +const Position: React.FunctionComponent<PositionItemProps> = (props: PositionItemProps) => { + const { position } = props; + return ( + <PositionWrap> + <StyledColumn width="50%"> + <Container position="relative" top="-3px" paddingRight="12px"> + <Heading asElement="h3" size="small" fontWeight="400" marginBottom="0"> + <a href={position.href} target="_blank"> + {position.title} + </a> + </Heading> + </Container> + </StyledColumn> + + <StyledColumn width="30%" padding="0 40px 0 0"> + <Paragraph isMuted={true} marginBottom="0"> + {position.location} + </Paragraph> + </StyledColumn> + + <StyledColumn width="20%"> + <Paragraph marginBottom="0" textAlign="right" color={colors.brandDark} fontWeight={400}> + <Link href={position.href} target="_blank"> + Apply + </Link> + </Paragraph> + </StyledColumn> + </PositionWrap> + ); +}; + +export interface NextAboutJobsProps {} +interface NextAboutJobsState { + jobInfos: WebsiteBackendJobInfo[]; +} + +export class NextAboutJobs extends React.Component<NextAboutJobsProps, NextAboutJobsState> { + private _isUnmounted: boolean; + private static _convertJobInfoToPositionProps(jobInfo: WebsiteBackendJobInfo): PositionProps { + return { + title: jobInfo.title, + location: jobInfo.office, + href: jobInfo.url, + }; + } + constructor(props: NextAboutJobsProps) { + super(props); + this.state = { + jobInfos: [], + }; + } + + public componentWillMount(): void { + // tslint:disable-next-line:no-floating-promises + this._fetchJobInfosAsync(); + } + public componentWillUnmount(): void { + this._isUnmounted = true; + } + public render(): React.ReactNode { + const positions = this.state.jobInfos.map(jobInfo => NextAboutJobs._convertJobInfoToPositionProps(jobInfo)); + return ( + <AboutPageLayout + title="Join Us in Our Mission" + description={ + <> + <Paragraph size="medium"> + To create a tokenized world where all value can flow freely. + </Paragraph> + <Paragraph size="medium"> + We are growing an ecosystem of businesses and projects by solving difficult challenges to + make our technology intuitive, flexible, and accessible to all. Join us in building + infrastructure upon which the exchange of all assets will take place. + </Paragraph> + </> + } + linkLabel="Our mission and values" + href={constants.URL_MISSION_AND_VALUES_BLOG_POST} + > + <DocumentTitle title="Jobs at 0x" /> + <Section bgColor="#F3F6F4" isFlex={true} maxWidth="1170px" wrapWidth="100%"> + <Column maxWidth="442px"> + <Heading size="medium" marginBottom="30px"> + Powered by a Diverse, Global Community + </Heading> + + <Paragraph> + We're a highly technical team with varied backgrounds in engineering, science, business, + finance, and research. While the Core Team is headquartered in San Francisco, there are 30+ + teams building on 0x and hundreds of thousands of participants behind our efforts worldwide. + We're passionate about open-source software and decentralized technology's potential to act + as an equalizing force in the world. + </Paragraph> + </Column> + + <Column maxWidth="600px"> + <ImageWrap> + <img src="/images/jobs/map@2x.png" height="365" alt="Map of community" /> + </ImageWrap> + </Column> + </Section> + + <Section isFlex={true} maxWidth="1170px" wrapWidth="100%"> + <Column> + <Heading size="medium">Benefits</Heading> + </Column> + + <Column maxWidth="826px"> + <BenefitsList> + <li>Comprehensive Insurance</li> + <li>Unlimited Vacation</li> + <li>Meals and snacks provided daily</li> + <li>Flexible hours and liberal work-from-home-policy</li> + <li>Supportive of remote working</li> + <li>Transportation, phone, and wellness expense</li> + <li>Relocation assistance</li> + <li>Optional team excursions</li> + <li>Competitive salary</li> + <li>Cryptocurrency based compensation</li> + </BenefitsList> + </Column> + </Section> + + <Section id={OPEN_POSITIONS_HASH} isFlex={true} maxWidth="1170px" wrapWidth="100%"> + <Column> + <Heading size="medium"> + Current<br />Openings + </Heading> + </Column> + + <Column maxWidth="826px"> + {_.map(positions, (position, index) => ( + <Position key={`position-${index}`} position={position} /> + ))} + </Column> + </Section> + </AboutPageLayout> + ); + } + private async _fetchJobInfosAsync(): Promise<void> { + try { + if (!this._isUnmounted) { + this.setState({ + jobInfos: [], + }); + } + const jobInfos = await backendClient.getJobInfosAsync(); + if (!this._isUnmounted) { + this.setState({ + jobInfos, + }); + } + } catch (error) { + if (!this._isUnmounted) { + this.setState({ + jobInfos: [], + }); + } + } + } +} + +const BenefitsList = styled.ul` + color: #000; + font-weight: 300; + line-height: 1.444444444; + list-style: disc; + columns: auto 2; + column-gap: 80px; + + li { + margin-bottom: 1em; + } +`; + +const ImageWrap = styled.figure` + @media (min-width: 768px) { + height: 600px; + padding-left: 60px; + display: flex; + align-items: flex-end; + } +`; + +const StyledColumn = styled(Column)` + flex-shrink: 0; + + @media (max-width: 768px) { + & + & { + margin-top: 15px; + } + } +`; + +const PositionWrap = styled(FlexWrap)` + margin-bottom: 40px; + padding-bottom: 30px; + position: relative; + + &:after { + content: ''; + width: 100%; + position: absolute; + bottom: 0; + left: 0; + height: 1px; + background-color: #e3e3e3; + } +`; diff --git a/packages/website/ts/pages/about/mission.tsx b/packages/website/ts/pages/about/mission.tsx new file mode 100644 index 000000000..ab8949fae --- /dev/null +++ b/packages/website/ts/pages/about/mission.tsx @@ -0,0 +1,97 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled from 'styled-components'; + +import { AboutPageLayout } from 'ts/components/aboutPageLayout'; +import { Definition } from 'ts/components/definition'; +import { Image } from 'ts/components/image'; +import { Column, Section } from 'ts/components/newLayout'; +import { Heading } from 'ts/components/text'; +import { constants } from 'ts/utils/constants'; + +const values = [ + { + title: 'Do The Right Thing', + description: + 'We acknowledge the broad subjectivity behind doing “the right thing,” and are committed to rigorously exploring its nuance in our decision making. We believe this responsibility drives our decision making above all else, and pledge to act in the best interest of our peers, community, and society as a whole.', + icon: 'right-thing', + }, + { + title: 'Consistently Ship', + description: + 'Achieving our mission requires dedication and diligence. We aspire to be an organization that consistently ships. We set high-impact goals that are rooted in data and pride ourselves in consistently outputting outstanding results across the organization.', + icon: 'consistently-ship', + }, + { + title: 'Focus on Long-term Impact', + description: + 'We anticipate that over time, awareness of the fundamentally disruptive nature of frictionless global exchange will cause some to see this technology as a threat. There will be setbacks, some will claim that this technology is too disruptive, and we will face adversity. Persistence and a healthy long-term focus will see us through these battles.', + icon: 'long-term-impact', + }, +]; + +export const NextAboutMission = () => ( + <AboutPageLayout + title="Creating a tokenized world where all value can flow freely." + description="0x is important infrastructure for the emerging crypto economy and enables markets to be created that couldn't have existed before. As more assets become tokenized, public blockchains provide the opportunity to establish a new financial stack that is more efficient, transparent, and equitable than any system in the past." + linkLabel="Our mission and values" + href={constants.URL_MISSION_AND_VALUES_BLOG_POST} + > + <DocumentTitle title="Our Mission - 0x" /> + <Section isFullWidth={true} isPadded={false}> + <FullWidthImage> + <Image src="/images/about/about-office.png" alt="0x Offices" isCentered={true} /> + </FullWidthImage> + </Section> + + <Section isFlex={true} maxWidth="1170px" wrapWidth="100%"> + <Column> + <Heading size="medium" maxWidth="226px"> + Core Values + </Heading> + </Column> + + <Column width="70%" maxWidth="826px"> + <Column width="100%" maxWidth="800px"> + {_.map(values, (item, index) => ( + <StyledDefinition + icon={item.icon} + title={item.title} + description={item.description} + isInlineIcon={true} + iconSize="large" + /> + ))} + </Column> + </Column> + </Section> + </AboutPageLayout> +); + +const StyledDefinition = styled(Definition)` + & + & { + margin-top: 30px; + padding-top: 30px; + border-top: 1px solid #eaeaea; + } +`; + +const FullWidthImage = styled.figure` + width: 100vw; + margin-left: calc(50% - 50vw); + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + @media (min-width: 768px) { + height: 500px; + } + + @media (max-width: 768px) { + height: 400px; + } +`; diff --git a/packages/website/ts/pages/about/press.tsx b/packages/website/ts/pages/about/press.tsx new file mode 100644 index 000000000..03003d656 --- /dev/null +++ b/packages/website/ts/pages/about/press.tsx @@ -0,0 +1,94 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled from 'styled-components'; + +import { AboutPageLayout } from 'ts/components/aboutPageLayout'; +import { Button } from 'ts/components/button'; +import { Column, FlexWrap } from 'ts/components/newLayout'; +import { Paragraph } from 'ts/components/text'; + +interface HighlightProps { + logo: string; + title?: string; + text: string; + href: string; +} + +interface HighlightItemProps { + highlight: HighlightProps; +} + +const highlights: HighlightProps[] = [ + { + logo: '/images/press/logo-forbes.png', + title: 'Forbes', + text: + '0x Instant is aiming to aid businesses and developers such as news sites, crypto wallets, dApps or price trackers to monetize or add a new revenue stream to their existing pipeline.', + href: + 'https://www.forbes.com/sites/rebeccacampbell1/2018/12/06/0x-launches-instant-delivers-an-easy-and-flexible-way-to-buy-crypto-tokens/#bfb73a843561', + }, + { + logo: '/images/press/logo-venturebeat.png', + title: 'VentureBeat', + text: '0x leads the way for ‘tokenization’ of the world, and collectible game items are next', + href: + 'https://venturebeat.com/2018/09/24/0x-leads-the-way-for-tokenization-of-the-world-and-collectible-game-items-are-next/', + }, + { + logo: '/images/press/logo-fortune.png', + title: 'Fortune', + text: + 'In the future, many traditional investments like real estate and corporate shares will come in the form of digital tokens that are bought and transferred on a blockchain.', + href: 'http://fortune.com/2018/09/06/0x-harbor-blockchain/', + }, + { + logo: '/images/press/logo-techcrunch.png', + title: 'TechCrunch', + text: + '0x allows any developer to quickly build their own decentralized cryptocurrency exchange and decide their own fees.', + href: 'https://techcrunch.com/2018/07/16/0x/', + }, +]; + +export const NextAboutPress = () => ( + <AboutPageLayout + title="Press Highlights" + description={ + <> + <Paragraph size="medium" marginBottom="60px"> + Want to write about 0x? <a href="mailto:team@0xproject.com">Get in touch.</a> + </Paragraph> + + {_.map(highlights, (highlight, index) => ( + <Highlight key={`highlight-${index}`} highlight={highlight} /> + ))} + </> + } + > + <DocumentTitle title="Press Highlights - 0x" /> + </AboutPageLayout> +); + +export const Highlight: React.FunctionComponent<HighlightItemProps> = (props: HighlightItemProps) => { + const { highlight } = props; + return ( + <HighlightWrap> + <Column> + <img src={highlight.logo} alt={highlight.title} /> + </Column> + + <Column width="60%" maxWidth="560px"> + <Paragraph isMuted={false}>{highlight.text}</Paragraph> + <Button href={highlight.href} isWithArrow={true} isNoBorder={true} target="_blank"> + Read Article + </Button> + </Column> + </HighlightWrap> + ); +}; + +const HighlightWrap = styled(FlexWrap)` + border-top: 1px solid #eaeaea; + padding: 30px 0; +`; diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx deleted file mode 100644 index 2361c6418..000000000 --- a/packages/website/ts/pages/about/profile.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { colors, Styles } from '@0x/react-shared'; -import * as _ from 'lodash'; -import * as React from 'react'; -import { ProfileInfo } from 'ts/types'; - -const IMAGE_DIMENSION = 149; -const styles: Styles = { - subheader: { - textTransform: 'uppercase', - fontSize: 32, - margin: 0, - }, - imageContainer: { - width: IMAGE_DIMENSION, - height: IMAGE_DIMENSION, - boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', - }, -}; - -interface ProfileProps { - colSize: number; - profileInfo: ProfileInfo; -} - -export const Profile = (props: ProfileProps) => { - return ( - <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> - <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> - <div className="circle overflow-hidden mx-auto" style={styles.imageContainer}> - <img width={IMAGE_DIMENSION} src={props.profileInfo.image} /> - </div> - <div className="center" style={{ fontSize: 18, fontWeight: 'bold', paddingTop: 20 }}> - {props.profileInfo.name} - </div> - {!_.isUndefined(props.profileInfo.title) && ( - <div - className="pt1 center" - style={{ - fontSize: 14, - fontFamily: 'Roboto Mono', - color: colors.darkGrey, - whiteSpace: 'nowrap', - }} - > - {props.profileInfo.title.toUpperCase()} - </div> - )} - <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> - {props.profileInfo.description} - </div> - <div className="flex pb3 sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> - {renderSocialMediaIcons(props.profileInfo)} - </div> - </div> - </div> - ); -}; - -function renderSocialMediaIcons(profileInfo: ProfileInfo): React.ReactNode { - const icons = [ - renderSocialMediaIcon('zmdi-github-box', profileInfo.github), - renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), - renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), - ]; - return icons; -} - -function renderSocialMediaIcon(iconName: string, url: string): React.ReactNode { - if (_.isEmpty(url)) { - return null; - } - - return ( - <div key={url} className="pr1"> - <a href={url} style={{ color: 'inherit' }} target="_blank" className="text-decoration-none"> - <i className={`zmdi ${iconName}`} style={{ ...styles.socalIcon }} /> - </a> - </div> - ); -} diff --git a/packages/website/ts/pages/about/team.tsx b/packages/website/ts/pages/about/team.tsx new file mode 100644 index 000000000..41d6c2cf8 --- /dev/null +++ b/packages/website/ts/pages/about/team.tsx @@ -0,0 +1,286 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled from 'styled-components'; + +import { colors } from 'ts/style/colors'; + +import { AboutPageLayout } from 'ts/components/aboutPageLayout'; +import { Column, Section } from 'ts/components/newLayout'; +import { Heading, Paragraph } from 'ts/components/text'; +import { WebsitePaths } from 'ts/types'; + +interface TeamMember { + name: string; + title: string; + imageUrl?: string; +} + +const team: TeamMember[] = [ + { + imageUrl: '/images/team/willw.jpg', + name: 'Will Warren', + title: 'co-founder & CEO', + }, + { + imageUrl: '/images/team/amirb.jpg', + name: 'Amir Bandeali', + title: 'Co-founder & CTO', + }, + { + imageUrl: '/images/team/fabiob.jpg', + name: 'Fabio Berger', + title: 'senior engineer', + }, + { + imageUrl: '/images/team/alexv.jpg', + name: 'Alex Xu', + title: 'Director of operations', + }, + { + imageUrl: '/images/team/leonidL.jpg', + name: 'Leonid Logvinov', + title: 'engineer', + }, + { + imageUrl: '/images/team/benb.jpg', + name: 'Ben Burns', + title: 'designer', + }, + { + imageUrl: '/images/team/brandonm.jpg', + name: 'Brandon Millman', + title: 'senior engineer', + }, + { + imageUrl: '/images/team/toms.jpg', + name: 'Tom Schmidt', + title: 'product manager', + }, + { + imageUrl: '/images/team/jacobe.jpg', + name: 'Jacob Evans', + title: 'ecosystem engineer', + }, + { + imageUrl: '/images/team/blake.jpg', + name: 'Blake Henderson', + title: 'ecosystem programs lead', + }, + { + imageUrl: '/images/team/zack.jpg', + name: 'Zack Skelly', + title: 'lead recruiter', + }, + { + imageUrl: '/images/team/greg.jpg', + name: 'Greg Hysen', + title: 'blockchain engineer', + }, + { + imageUrl: '/images/team/remcoB.jpg', + name: 'Remco Bloemen', + title: 'technical fellow', + }, + { + imageUrl: '/images/team/francesco.jpg', + name: 'Francesco Agosti', + title: 'engineer', + }, + { + imageUrl: '/images/team/melo.jpg', + name: 'Mel Oberto', + title: 'people operations associate', + }, + { + imageUrl: '/images/team/alexb.jpg', + name: 'Alex Browne', + title: 'engineer in residence', + }, + { + imageUrl: '/images/team/peterz.jpg', + name: 'Peter Zeitz', + title: 'research fellow', + }, + { + imageUrl: '/images/team/chrisk.jpg', + name: 'Chris Kalani', + title: 'director of design', + }, + { + imageUrl: '/images/team/clayr.jpg', + name: 'Clay Robbins', + title: 'ecosystem development lead', + }, + { + imageUrl: '/images/team/mattt.jpg', + name: 'Matt Taylor', + title: 'marketing lead', + }, + { + imageUrl: '/images/team/eugenea.jpg', + name: 'Eugene Aumson', + title: 'engineer', + }, + { + imageUrl: '/images/team/weijew.jpg', + name: 'Weijie Wu', + title: 'research fellow', + }, + { + imageUrl: '/images/team/rahuls.jpg', + name: 'Rahul Singireddy', + title: 'relayer success manager', + }, + { + imageUrl: '/images/team/jasons.jpg', + name: 'Jason Somensatto', + title: 'strategic legal counsel', + }, + { + imageUrl: '/images/team/steveK.jpg', + name: 'Steve Klebanoff', + title: 'senior engineer', + }, + { + imageUrl: '/images/team/xianny.jpg', + name: 'Xianny Ng', + title: 'engineer', + }, +]; + +const advisors: TeamMember[] = [ + { + imageUrl: '/images/team/advisors/frede.jpg', + name: 'Fred Ehrsam', + title: 'Advisor', + }, + { + imageUrl: '/images/team/advisors/olafc.jpg', + name: 'Olaf Carlson-Wee', + title: 'Advisor', + }, + { + imageUrl: '/images/team/advisors/joeyk.jpg', + name: 'Joey Krug', + title: 'Advisor', + }, + { + imageUrl: '/images/team/advisors/lindax.jpg', + name: 'Linda Xie', + title: 'Advisor', + }, + { + imageUrl: '/images/team/advisors/davids.jpg', + name: 'David Sacks', + title: 'Advisor', + }, +]; + +export const NextAboutTeam = () => ( + <AboutPageLayout + title="We are a global, growing team" + description="We are a distributed team with backgrounds in engineering, academic research, business, and design. The 0x Core Team is passionate about accelerating the adoption decentralized technology and believe in its potential to be an equalizing force in the world. Join us and do the most impactful work of your life." + linkLabel="Join the team" + to={WebsitePaths.AboutJobs} + > + <DocumentTitle title="Our Team - 0x" /> + <Section maxWidth="1170px" wrapWidth="100%" isFlex={true} flexBreakpoint="900px"> + <Column> + <Heading size="medium">0x Team</Heading> + </Column> + + <Column width="70%" maxWidth="800px"> + <StyledGrid> + {_.map(team, (info: TeamMember, index: number) => ( + <Member key={`team-${index}`} name={info.name} title={info.title} imageUrl={info.imageUrl} /> + ))} + </StyledGrid> + </Column> + </Section> + + <Section bgColor="#F3F6F4" maxWidth="1170px" wrapWidth="100%" flexBreakpoint="900px" isFlex={true}> + <Column> + <Heading size="medium">Advisors</Heading> + </Column> + + <Column width="70%" maxWidth="800px"> + <StyledGrid> + {_.map(advisors, (info: TeamMember, index: number) => ( + <Member key={`advisor-${index}`} name={info.name} title={info.title} imageUrl={info.imageUrl} /> + ))} + </StyledGrid> + </Column> + </Section> + </AboutPageLayout> +); + +const StyledGrid = styled.div` + &:after { + content: ''; + clear: both; + } +`; + +const Member = ({ name, title, imageUrl }: TeamMember) => ( + <StyledMember> + <img src={imageUrl} alt={name} /> + <Name>{name}</Name> + <MemberTitle isMuted={0.5} size={14} style={{ textTransform: 'capitalize' }}> + {title} + </MemberTitle> + </StyledMember> +); + +const StyledMember = styled.div` + margin-bottom: 10px; + float: left; + width: calc(50% - 15px); + margin-right: 15px; + + @media (max-width: 600px) { + &:nth-child(2n + 1) { + clear: left; + } + } + + img, + svg { + width: 100%; + height: auto; + object-fit: contain; + margin-bottom: 10px; + } + + @media (min-width: 600px) { + width: calc(33.3333% - 30px); + margin-right: 20px; + + &:nth-child(3n + 1) { + clear: left; + } + } + + @media (min-width: 900px) { + width: calc(25% - 30px); + + &:nth-child(3n + 1) { + clear: none; + } + + &:nth-child(4n + 1) { + clear: left; + } + } +`; + +const Name = styled.h3` + color: ${colors.textDarkPrimary}; + font-size: 14px; + line-height: 1; + margin: 0; +`; + +const MemberTitle = styled(Paragraph)` + font-size: 14px; +`; diff --git a/packages/website/ts/pages/community.tsx b/packages/website/ts/pages/community.tsx new file mode 100644 index 000000000..7c02fed82 --- /dev/null +++ b/packages/website/ts/pages/community.tsx @@ -0,0 +1,289 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import styled from 'styled-components'; + +import { colors } from 'ts/style/colors'; + +import { Banner } from 'ts/components/banner'; +import { Button } from 'ts/components/button'; +import { Icon } from 'ts/components/icon'; +import { ModalContact } from 'ts/components/modals/modal_contact'; +import { Column, Section, WrapGrid } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; +import { Heading, Paragraph } from 'ts/components/text'; + +interface EventProps { + title: string; + date: string; + signupUrl: string; + imageUrl: string; +} + +interface CommunityLinkProps { + bgColor: string; + title?: string; + icon?: string; + url: string; +} + +const events: EventProps[] = [ + { + title: '0x London Meetup', + date: 'October 20th 2018', + imageUrl: '/images/events/london.jpg', + signupUrl: '#', + }, + { + title: '0x Berlin Meetup', + date: 'October 20th 2018', + imageUrl: '/images/events/berlin.jpg', + signupUrl: '#', + }, + { + title: '0x San Francisco Meetup', + date: 'October 20th 2018', + imageUrl: '/images/events/sf.jpg', + signupUrl: '#', + }, +]; +const communityLinks: CommunityLinkProps[] = [ + { + bgColor: '#1DA1F2', + title: 'Twitter', + icon: 'social-twitter', + url: 'https://twitter.com/0xProject', + }, + { + bgColor: '#FF4500', + title: 'Reddit', + icon: 'social-reddit', + url: 'https://twitter.com/0xProject', + }, + { + bgColor: '#7289DA', + title: 'Twitter', + icon: 'social-discord', + url: 'https://twitter.com/0xProject', + }, + { + bgColor: '#3B5998', + title: 'Facebook', + icon: 'social-fb', + url: 'https://twitter.com/0xProject', + }, + { + bgColor: '#181717', + title: 'GitHub', + icon: 'social-github', + url: 'https://twitter.com/0xProject', + }, + { + bgColor: '#003831', + title: 'Newsletter', + icon: 'social-newsletter', + url: 'https://twitter.com/0xProject', + }, +]; + +export class NextCommunity extends React.Component { + public state = { + isContactModalOpen: false, + }; + public render(): React.ReactNode { + return ( + <SiteWrap theme="light"> + <Section isTextCentered={true}> + <Column> + <Heading size="medium" isCentered={true}> + Community + </Heading> + <Paragraph size="medium" isCentered={true} isMuted={true} marginBottom="0"> + The 0x community is a global, passionate group of crypto developers and enthusiasts. The + official channels below provide a great forum for connecting and engaging with the + community. + </Paragraph> + <LinkWrap> + <Button to="#" isWithArrow={true} isAccentColor={true}> + Join the 0x community + </Button> + </LinkWrap> + </Column> + </Section> + + <Section isFullWidth={true}> + <WrapGrid + isTextCentered={true} + isWrapped={true} + isFullWidth={false} + isCentered={false} + maxWidth="1151px" + > + {_.map(communityLinks, (link: CommunityLinkProps, index: number) => ( + <CommunityLink + key={`cl-${index}`} + icon={link.icon} + title={link.title} + bgColor={link.bgColor} + url={link.url} + /> + ))} + </WrapGrid> + </Section> + + <EventsWrapper + bgColor={colors.backgroundLight} + isFullWidth={true} + isCentered={true} + isTextCentered={true} + > + <Column maxWidth="720px"> + <Heading size="medium" asElement="h2" isCentered={true} maxWidth="507px" marginBottom="30px"> + Upcoming Events + </Heading> + <Paragraph size="medium" isCentered={true} isMuted={true}> + 0x meetups happen all over the world on a monthly basis and are hosted by devoted members of + the community. Want to host a meetup in your city? Reach out for help finding a venue, + connecting with local 0x mentors, and promoting your events. + </Paragraph> + <LinkWrap> + <Button to="#" isWithArrow={true} isAccentColor={true}> + Get in Touch + </Button> + <Button to="#" isWithArrow={true} isAccentColor={true}> + Join Newsletter + </Button> + </LinkWrap> + </Column> + <WrapGrid + isTextCentered={true} + isWrapped={true} + isFullWidth={false} + isCentered={false} + maxWidth="1149px" + > + {_.map(events, (ev: EventProps, index: number) => ( + <Event + key={`event-${index}`} + title={ev.title} + date={ev.date} + signupUrl={ev.signupUrl} + imageUrl={ev.imageUrl} + /> + ))} + </WrapGrid> + </EventsWrapper> + + <Banner + heading="Ready to get started?" + subline="Dive into our docs, or contact us if needed" + mainCta={{ text: 'Get Started', href: '/docs' }} + secondaryCta={{ text: 'Get in Touch', onClick: this._onOpenContactModal.bind(this) }} + /> + <ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} /> + </SiteWrap> + ); + } + + public _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + public _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; +} + +const Event: React.FunctionComponent<EventProps> = (event: EventProps) => ( + <StyledEvent> + <EventIcon name="logo-mark" size={30} margin={0} /> + <EventImage src={event.imageUrl} alt="" /> + <EventContent> + <Heading color={colors.white} size="small" marginBottom="0"> + {event.title} + </Heading> + <Paragraph color={colors.white} isMuted={0.65}> + {event.date} + </Paragraph> + <Button color={colors.white} href={event.signupUrl} isWithArrow={true}> + Sign Up + </Button> + </EventContent> + </StyledEvent> +); + +const CommunityLink: React.FunctionComponent<CommunityLinkProps> = (props: CommunityLinkProps) => ( + <StyledCommunityLink bgColor={props.bgColor} href={props.url}> + <CommunityIcon name={props.icon} size={44} margin={0} /> + <CommunityTitle color={colors.white} isMuted={false} marginBottom="0"> + {props.title} + </CommunityTitle> + </StyledCommunityLink> +); + +// Events +const EventsWrapper = styled(Section)` + display: flex; + align-items: center; + flex-direction: column; +`; + +// Event +const StyledEvent = styled.div` + background-color: ${colors.brandDark}; + width: calc((100% / 3) - 30px); + text-align: left; + height: 424px; + margin-top: 130px; + position: relative; +`; + +const EventIcon = styled(Icon)` + position: absolute; + top: 30px; + left: 30px; +`; + +const EventImage = styled.img` + width: 100%; + height: 260px; + object-fit: cover; +`; + +const EventContent = styled.div` + padding: 30px 30px; +`; + +interface StyledCommunityLinkProps { + bgColor: string; +} +const StyledCommunityLink = styled.a` + background-color: ${(props: StyledCommunityLinkProps) => props.bgColor}; + color: ${colors.white}; + width: 175px; + height: 175px; + text-align: center; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const CommunityTitle = styled(Paragraph)` + font-size: 20px; + font-weight: 400; +`; + +const CommunityIcon = styled(Icon)` + margin-bottom: 20px; +`; + +// Misc +const LinkWrap = styled.div` + display: inline-flex; + margin-top: 60px; + + a + a { + margin-left: 60px; + } +`; diff --git a/packages/website/ts/pages/documentation/developers_page.tsx b/packages/website/ts/pages/documentation/developers_page.tsx index a84be7bfe..fcca2b6ad 100644 --- a/packages/website/ts/pages/documentation/developers_page.tsx +++ b/packages/website/ts/pages/documentation/developers_page.tsx @@ -2,6 +2,7 @@ import { colors, constants as sharedConstants, utils as sharedUtils } from '@0x/ import * as _ from 'lodash'; import * as React from 'react'; import DocumentTitle from 'react-document-title'; +import { Helmet } from 'react-helmet'; import { DocsLogo } from 'ts/components/documentation/docs_logo'; import { DocsTopBar } from 'ts/components/documentation/docs_top_bar'; import { Container } from 'ts/components/ui/container'; @@ -146,6 +147,9 @@ export class DevelopersPage extends React.Component<DevelopersPageProps, Develop } 50%, ${colors.white} 100%)`} > <DocumentTitle title="0x Docs" /> + <Helmet> + <link rel="stylesheet" href="/css/github-gist.css" /> + </Helmet> <Container className="flex mx-auto" height="100vh"> <Container className="sm-hide xs-hide relative" diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index adf2261b5..3ae071774 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -39,6 +39,7 @@ const docIdToSubpackageName: { [id: string]: string } = { [DocPackages.OrderWatcher]: 'order-watcher', [DocPackages.EthereumTypes]: 'ethereum-types', [DocPackages.AssetBuyer]: 'asset-buyer', + [DocPackages.Migrations]: 'migrations', }; export interface DocPageProps { diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index e3328f3fa..fd3932bfa 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -44,13 +44,21 @@ const TUTORIALS: TutorialInfo[] = [ to: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`, }, }, + { + iconUrl: '/images/developers/tutorials/integrate_0x_instant.svg', + description: Key.Integrate0xInstantDescription, + link: { + title: Key.Integrate0xInstant, + to: `${WebsitePaths.Wiki}#Get-Started-With-Instant`, + }, + }, ]; const CATEGORY_TO_PACKAGES: ObjectMap<Package[]> = { [Categories.ZeroExProtocol]: [ { description: - 'A library for interacting with the 0x protocol. It is a high level package which combines a number of smaller specific-purpose packages such as [order-utils](https://0xproject.com/docs/order-utils) and [contract-wrappers](https://0xproject.com/docs/contract-wrappers).', + 'A library for interacting with the 0x protocol. It is a high level package which combines a number of smaller specific-purpose packages such as [order-utils](https://0x.org/docs/order-utils) and [contract-wrappers](https://0x.org/docs/contract-wrappers).', link: { title: '0x.js', to: WebsitePaths.ZeroExJs, @@ -92,6 +100,14 @@ const CATEGORY_TO_PACKAGES: ObjectMap<Package[]> = { }, }, { + description: 'A Python Standard Relayer API client', + link: { + title: '0x-sra-client.py', + to: 'https://pypi.org/project/0x-sra-client/', + shouldOpenInNewTab: true, + }, + }, + { description: 'An http & websocket client for interacting with relayers that have implemented the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api)', link: { @@ -109,6 +125,14 @@ const CATEGORY_TO_PACKAGES: ObjectMap<Package[]> = { }, { description: + "A package to deploy the 0x protocol's system of smart contracts to the testnet of your choice", + link: { + title: '@0x/migrations', + to: WebsitePaths.Migrations, + }, + }, + { + description: 'A collection of 0x-related JSON-schemas (incl. SRA request/response schemas, 0x order message format schema, etc...)', link: { title: '@0x/json-schemas', @@ -133,6 +157,23 @@ const CATEGORY_TO_PACKAGES: ObjectMap<Package[]> = { }, { description: + 'A tiny utility library for getting known deployed contract addresses for a particular network.', + link: { + title: '@0x/contract-addresses', + to: 'https://www.npmjs.com/package/@0x/contract-addresses', + shouldOpenInNewTab: true, + }, + }, + { + description: 'Smart contract compilation artifacts for the latest version of the 0x protocol.', + link: { + title: '@0x/contract-artifacts', + to: 'https://www.npmjs.com/package/@0x/contract-artifacts', + shouldOpenInNewTab: true, + }, + }, + { + description: 'Contains the Standard Relayer API OpenAPI Spec. The package distributes both a javascript object version and a json version.', link: { title: '@0x/sra-spec', diff --git a/packages/website/ts/pages/ecosystem.tsx b/packages/website/ts/pages/ecosystem.tsx new file mode 100644 index 000000000..8e367b21f --- /dev/null +++ b/packages/website/ts/pages/ecosystem.tsx @@ -0,0 +1,128 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled from 'styled-components'; + +import { colors } from 'ts/style/colors'; + +import { Button } from 'ts/components/button'; +import { Icon } from 'ts/components/icon'; +import { Column, Section, WrapGrid } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; +import { Heading, Paragraph } from 'ts/components/text'; +import { constants } from 'ts/utils/constants'; + +interface BenefitProps { + title: string; + icon: string; + description: string; +} + +const benefits: BenefitProps[] = [ + { + icon: 'milestoneGrants', + title: 'Milestone Grants', + description: + 'Receive non-dilutive capital ranging from $10,000 to $100,000, with grant sizes awarded based on the quality of your team, vision, execution, and community involvement.', + }, + { + icon: 'vcIntroductions', + title: 'VC Introductions', + description: 'Connect with leading venture capital firms that could participate in your next funding round.', + }, + { + icon: 'techSupport', + title: 'Technical Support', + description: 'Receive ongoing technical assistance from knowledgeable and responsive 0x developers.', + }, + { + icon: 'recruitingSupport', + title: 'Recruiting Assistance', + description: 'Grow your team by accessing an exclusive pool of top engineering and business operations talent.', + }, + { + icon: 'eficientDesign', + title: 'Marketing and Design Help', + description: + 'Get strategic advice on product positioning, customer acquisition, and UI/UX design that can impact the growth of your business.', + }, + { + icon: 'legalResources', + title: 'Legal Resources', + description: 'Access important legal resources that will help you navigate the regulatory landscape.', + }, +]; + +export const NextEcosystem = () => ( + <SiteWrap theme="light"> + <DocumentTitle title="Ecosystem Acceleration Program: Jumpstart your Business on 0x" /> + <Section isTextCentered={true}> + <Column> + <Heading size="medium" isCentered={true}> + Jumpstart your Business on 0x + </Heading> + <Paragraph size="medium" isCentered={true} isMuted={true} marginBottom="0"> + The Ecosystem Acceleration Program gives teams access to a variety of services including funding, + dedicated technical support, and recruiting assistance. We created the Ecosystem Acceleration + Program to bolster the expansion of both infrastructure projects and relayers building on 0x. + </Paragraph> + <LinkWrap> + <Button + href={constants.URL_ECOSYSTEM_APPLY} + isWithArrow={true} + isAccentColor={true} + shouldUseAnchorTag={true} + > + Apply now + </Button> + <Button + href={constants.URL_ECOSYSTEM_BLOG_POST} + isWithArrow={true} + isAccentColor={true} + shouldUseAnchorTag={true} + target="_blank" + > + Learn More + </Button> + </LinkWrap> + </Column> + </Section> + + <Section bgColor={colors.backgroundLight} isFullWidth={true}> + <Column> + <Heading + size={34} + fontWeight="400" + asElement="h2" + isCentered={true} + maxWidth="507px" + marginBottom="70px" + > + Join a vibrant ecosystem of projects in the 0x Network. + </Heading> + </Column> + <WrapGrid isTextCentered={true} isWrapped={true} isFullWidth={true}> + {_.map(benefits, (benefit: BenefitProps, index) => ( + <Column key={`benefit-${index}`} width="33%" padding="0 45px 30px"> + <Icon name={benefit.icon} size="medium" margin={[0, 0, 'small', 0]} /> + <Heading color={colors.textDarkPrimary} size="small" marginBottom="10px" isCentered={true}> + {benefit.title} + </Heading> + <Paragraph isMuted={true} isCentered={true}> + {benefit.description} + </Paragraph> + </Column> + ))} + </WrapGrid> + </Section> + </SiteWrap> +); + +const LinkWrap = styled.div` + display: inline-flex; + margin-top: 60px; + + a + a { + margin-left: 60px; + } +`; diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 10d91bae8..8cde7224e 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -2,7 +2,7 @@ import { colors, Styles } from '@0x/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; -import { Footer } from 'ts/components/footer'; +import { Footer } from 'ts/components/old_footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Question } from 'ts/pages/faq/question'; import { Dispatcher } from 'ts/redux/dispatcher'; @@ -379,7 +379,7 @@ const sections: FAQSection[] = [ <div> Join our{' '} <a href={constants.URL_ZEROEX_CHAT} target="_blank"> - Rocket.chat + Discord </a>! As an open source project, 0x will rely on a worldwide community of passionate developers to contribute proposals, ideas and code. </div> diff --git a/packages/website/ts/pages/instant.tsx b/packages/website/ts/pages/instant.tsx new file mode 100644 index 000000000..b97bb35a4 --- /dev/null +++ b/packages/website/ts/pages/instant.tsx @@ -0,0 +1,261 @@ +import { utils as sharedUtils } from '@0x/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import styled, { keyframes } from 'styled-components'; + +import { Banner } from 'ts/components/banner'; +import { Button } from 'ts/components/button'; +import { Definition } from 'ts/components/definition'; +import { Hero } from 'ts/components/hero'; +import { Section, SectionProps } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; +import { Heading, Paragraph } from 'ts/components/text'; +import { Configurator } from 'ts/pages/instant/configurator'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; +import { utils } from 'ts/utils/utils'; + +import { ModalContact } from '../components/modals/modal_contact'; + +const CONFIGURATOR_MIN_WIDTH_PX = 1050; + +export const getStartedClick = () => { + if (window.innerWidth < CONFIGURATOR_MIN_WIDTH_PX) { + utils.openUrl(`${WebsitePaths.Wiki}#Get-Started-With-Instant`); + } else { + sharedUtils.setUrlHash('configurator'); + sharedUtils.scrollToHash('configurator', ''); + } +}; + +const featuresData = [ + { + title: 'Support ERC-20 and ERC-721 tokens', + icon: 'supportForAllEthereumStandards-large', + description: + 'Seamlessly integrate token purchasing into your product experience by offering digital assets ranging from in-game items to stablecoins.', + links: [ + { + label: 'Get Started', + onClick: getStartedClick, + shouldUseAnchorTag: true, + }, + { + label: 'Explore the Docs', + url: `${WebsitePaths.Wiki}#Get-Started-With-Instant`, + }, + ], + }, + { + title: 'Generate revenue for your business', + icon: 'generateRevenueForYourBusiness-large', + description: + 'With just a few lines of code, you can earn up to 5% in affiliate fees on every transaction from your crypto wallet or dApp.', + links: [ + { + label: 'Learn about affiliate fees', + url: `${WebsitePaths.Wiki}#Learn-About-Affiliate-Fees`, + }, + ], + }, + { + title: 'Easy and flexible integration', + icon: 'flexibleIntegration0xInstant', + description: + 'Use our out-of-the-box design or customize the user interface by integrating via the AssetBuyer engine.. You can also tap into 0x networked liquidity or choose your own liquidity pool.', + links: [ + { + label: 'Explore AssetBuyer', + url: `${WebsitePaths.Docs}/asset-buyer`, + }, + ], + }, +]; + +interface Props { + theme: { + bgColor: string; + textColor: string; + linkColor: string; + }; +} + +export class Next0xInstant extends React.Component<Props> { + public state = { + isContactModalOpen: false, + }; + public render(): React.ReactNode { + return ( + <SiteWrap> + <DocumentTitle title="0x Instant: Quick and secure crypto purchasing" /> + <Hero + title="Introducing 0x Instant" + description="A free and flexible way to offer simple crypto purchasing in any app or website" + actions={<Button onClick={getStartedClick}>Get Started</Button>} + /> + + <Section isFullWidth={true} isPadded={false} padding="30px 0"> + <MarqueeWrap> + <div> + {[...Array(18)].map((item, index) => ( + <Card key={`card-${index}`} index={index}> + <img src={`/images/0x-instant/widget-${index % 6 + 1}.png`} /> + </Card> + ))} + </div> + </MarqueeWrap> + </Section> + + <Section> + {_.map(featuresData, (item, index) => ( + <Definition + key={`definition-${index}`} + icon={item.icon} + title={item.title} + description={item.description} + isInlineIcon={true} + iconSize={240} + actions={item.links} + /> + ))} + </Section> + + <ConfiguratorSection + id="configurator" + maxWidth="1386px" + padding="0 58px 70px" + bgColor={colors.backgroundDark} + > + <Heading>0x Instant Configurator</Heading> + <Configurator /> + </ConfiguratorSection> + + <Banner + heading="Need more flexibility?" + subline="Dive into our docs, or contact us if needed" + mainCta={{ text: 'Explore the Docs', href: `${WebsitePaths.Wiki}#Get-Started-With-Instant` }} + secondaryCta={{ text: 'Get in Touch', onClick: this._onOpenContactModal.bind(this) }} + /> + <ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} /> + + <Section maxWidth="1170px" isPadded={false} padding="60px 0"> + <Paragraph size="small" isMuted={0.5}> + Disclaimer: The laws and regulations applicable to the use and exchange of digital assets and + blockchain-native tokens, including through any software developed using the licensed work + created by ZeroEx Intl. (the “Work”), vary by jurisdiction. As set forth in the Apache License, + Version 2.0 applicable to the Work, developers are “solely responsible for determining the + appropriateness of using or redistributing the Work,” which includes responsibility for ensuring + compliance with any such applicable laws and regulations. + </Paragraph> + <Paragraph size="small" isMuted={0.5}> + See the Apache License, Version 2.0 for the specific language governing all applicable + permissions and limitations. + </Paragraph> + </Section> + </SiteWrap> + ); + } + + public _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + public _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; +} + +// scroll animation calc is simply (imageWidth * totalRepetitions) / 2 +// img width is 370px +const scroll = keyframes` + 0% { transform: translate3d(-2220px, 0, 0) } + 100% { transform: translate3d(-4440px, 0, 0) } +`; + +const scrollMobile = keyframes` + 0% { transform: translate3d(0, 0, 0) } + 100% { transform: translate3d(-1800px, 0, 0) } +`; + +const fadeUp = keyframes` + 0% { + opacity: 0; + transform: translateY(50px); + } + 100% { + opacity: 1; + transform: translateY(0px); + } +`; + +const ConfiguratorSection = + styled(Section) < + SectionProps > + ` + @media (max-width: ${CONFIGURATOR_MIN_WIDTH_PX}px) { + display: none; + } +`; + +// width = 370 * 12 +// mobile width = 300 +const MarqueeWrap = styled.div` + width: 100vw; + height: 514px; + padding-bottom: 60px; + + @media (max-width: 768px) { + width: calc(100% + 60px); + margin-left: -30px; + overflow: hidden; + } + + > div { + height: auto; + display: flex; + will-change: transform; + transform: translate3d(-2220px, 0, 0); + } + + @media (min-width: 768px) { + > div { + width: 6660px; + animation: ${scroll} 70s linear infinite; + } + } + + @media (max-width: 768px) { + > div { + width: 5400px; + animation: ${scrollMobile} 70s linear infinite; + } + } +`; + +const Card = + styled.div < + { index: number } > + ` + opacity: 0; + flex-shrink: 0; + transform: translateY(10px); + will-change: opacity, transform; + animation: ${fadeUp} 0.75s ${props => `${props.index * 0.05}s`} forwards; + + img { + height: auto; + } + + @media (min-width: 768px) { + img { + width: 370px; + } + } + + @media (max-width: 768px) { + img { + width: 300px; + } + } +`; diff --git a/packages/website/ts/pages/instant/code_demo.tsx b/packages/website/ts/pages/instant/code_demo.tsx new file mode 100644 index 000000000..c59f148b8 --- /dev/null +++ b/packages/website/ts/pages/instant/code_demo.tsx @@ -0,0 +1,183 @@ +import * as React from 'react'; +import * as CopyToClipboard from 'react-copy-to-clipboard'; +import SyntaxHighlighter from 'react-syntax-highlighter'; + +import { Button } from 'ts/components/button'; +import { Container } from 'ts/components/ui/container'; +import { styled } from 'ts/style/theme'; +import { zIndex } from 'ts/style/z_index'; + +const CustomPre = styled.pre` + margin: 0px; + line-height: 24px; + overflow: scroll; + width: 100%; + height: 100%; + max-height: 800px; + border-radius: 4px; + code { + background-color: inherit !important; + border-radius: 0px; + font-family: 'Roboto Mono', sans-serif; + border: none; + } + code:first-of-type { + background-color: #060d0d !important; + color: #999; + min-height: 100%; + text-align: center; + margin-right: 15px; + line-height: 25px; + padding: 10px 7px !important; + } + code:last-of-type { + position: relative; + top: 10px; + top: 0; + padding-top: 11px; + display: inline-block; + line-height: 25px; + } +`; + +const customStyle = { + 'hljs-comment': { + color: '#7e7887', + }, + 'hljs-quote': { + color: '#7e7887', + }, + 'hljs-variable': { + color: '#be4678', + }, + 'hljs-template-variable': { + color: '#be4678', + }, + 'hljs-attribute': { + color: '#be4678', + }, + 'hljs-regexp': { + color: '#be4678', + }, + 'hljs-link': { + color: '#be4678', + }, + 'hljs-tag': { + color: '#61f5ff', + }, + 'hljs-name': { + color: '#61f5ff', + }, + 'hljs-selector-id': { + color: '#be4678', + }, + 'hljs-selector-class': { + color: '#be4678', + }, + 'hljs-number': { + color: '#c994ff', + }, + 'hljs-meta': { + color: '#61f5ff', + }, + 'hljs-built_in': { + color: '#aa573c', + }, + 'hljs-builtin-name': { + color: '#aa573c', + }, + 'hljs-literal': { + color: '#aa573c', + }, + 'hljs-type': { + color: '#aa573c', + }, + 'hljs-params': { + color: '#aa573c', + }, + 'hljs-string': { + color: '#bcff88', + }, + 'hljs-symbol': { + color: '#2a9292', + }, + 'hljs-bullet': { + color: '#2a9292', + }, + 'hljs-title': { + color: '#576ddb', + }, + 'hljs-section': { + color: '#576ddb', + }, + 'hljs-keyword': { + color: '#955ae7', + }, + 'hljs-selector-tag': { + color: '#955ae7', + }, + 'hljs-deletion': { + color: '#19171c', + display: 'inline-block', + width: '100%', + backgroundColor: '#be4678', + }, + 'hljs-addition': { + color: '#19171c', + display: 'inline-block', + width: '100%', + backgroundColor: '#2a9292', + }, + hljs: { + display: 'block', + overflowX: 'hidden', + background: '#1B2625', + color: 'white', + fontSize: '12px', + }, + 'hljs-emphasis': { + fontStyle: 'italic', + }, + 'hljs-strong': { + fontWeight: 'bold', + }, +}; + +export interface CodeDemoProps { + children: string; +} + +export interface CodeDemoState { + didCopyCode: boolean; +} + +export class CodeDemo extends React.Component<CodeDemoProps, CodeDemoState> { + public state: CodeDemoState = { + didCopyCode: false, + }; + public render(): React.ReactNode { + const copyButtonText = this.state.didCopyCode ? 'Copied!' : 'Copy'; + return ( + <Container position="relative" height="100%"> + <Container position="absolute" top="10px" right="10px" zIndex={zIndex.overlay - 1}> + <CopyToClipboard text={this.props.children} onCopy={this._handleCopyClick}> + <StyledButton>{copyButtonText}</StyledButton> + </CopyToClipboard> + </Container> + <SyntaxHighlighter language="html" style={customStyle} showLineNumbers={true} PreTag={CustomPre}> + {this.props.children} + </SyntaxHighlighter> + </Container> + ); + } + private readonly _handleCopyClick = () => { + this.setState({ didCopyCode: true }); + }; +} + +const StyledButton = styled(Button)` + border-radius: 4px; + font-size: 15px; + font-weight: 400; + padding: 9px 21px 7px; +`; diff --git a/packages/website/ts/pages/instant/config_generator.tsx b/packages/website/ts/pages/instant/config_generator.tsx new file mode 100644 index 000000000..e43d47119 --- /dev/null +++ b/packages/website/ts/pages/instant/config_generator.tsx @@ -0,0 +1,332 @@ +import { StandardRelayerAPIOrderProvider } from '@0x/asset-buyer'; +import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; +import { assetDataUtils } from '@0x/order-utils'; +import { ObjectMap } from '@0x/types'; +import * as _ from 'lodash'; +import * as React from 'react'; +import styled from 'styled-components'; + +import { CheckMark } from 'ts/components/ui/check_mark'; +import { Container } from 'ts/components/ui/container'; +import { MultiSelect } from 'ts/components/ui/multi_select'; +import { Spinner } from 'ts/components/ui/spinner'; +import { Text } from 'ts/components/ui/text'; +import { ConfigGeneratorAddressInput } from 'ts/pages/instant/config_generator_address_input'; +import { FeePercentageSlider } from 'ts/pages/instant/fee_percentage_slider'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; +import { constants } from 'ts/utils/constants'; + +// New components +import { Heading } from 'ts/components/text'; +import { Select, SelectItemConfig } from 'ts/pages/instant/select'; + +import { assetMetaDataMap } from '../../../../instant/src/data/asset_meta_data_map'; +import { ERC20AssetMetaData, ZeroExInstantBaseConfig } from '../../../../instant/src/types'; + +export interface ConfigGeneratorProps { + value: ZeroExInstantBaseConfig; + onConfigChange: (config: ZeroExInstantBaseConfig) => void; +} + +export interface ConfigGeneratorState { + isLoadingAvailableTokens: boolean; + // Address to token info + availableTokens?: ObjectMap<ERC20AssetMetaData>; +} + +const SRA_ENDPOINTS = ['https://api.radarrelay.com/0x/v2/', 'https://sra.bamboorelay.com/0x/v2/']; + +export class ConfigGenerator extends React.Component<ConfigGeneratorProps, ConfigGeneratorState> { + public state: ConfigGeneratorState = { + isLoadingAvailableTokens: true, + }; + public componentDidMount(): void { + // tslint:disable-next-line:no-floating-promises + this._setAvailableAssetsFromOrderProvider(); + } + public componentDidUpdate(prevProps: ConfigGeneratorProps): void { + if (prevProps.value.orderSource !== this.props.value.orderSource) { + // tslint:disable-next-line:no-floating-promises + this._setAvailableAssetsFromOrderProvider(); + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: undefined, + }; + this.props.onConfigChange(newConfig); + } + } + public render(): React.ReactNode { + const { value } = this.props; + if (!_.isString(value.orderSource)) { + throw new Error('ConfigGenerator component only supports string values as an orderSource.'); + } + return ( + <Container minWidth="350px"> + <ConfigGeneratorSection title="Liquidity Source"> + <Select + shouldIncludeEmpty={false} + id="" + value={value.orderSource} + items={this._generateItems()} + onChange={this._handleSRASelection.bind(this)} + /> + </ConfigGeneratorSection> + <ConfigGeneratorSection {...this._getTokenSelectorProps()}> + {this._renderTokenMultiSelectOrSpinner()} + </ConfigGeneratorSection> + <ConfigGeneratorSection title="Transaction fee ETH address" marginBottom="10px" isOptional={true}> + <ConfigGeneratorAddressInput + value={value.affiliateInfo ? value.affiliateInfo.feeRecipient : ''} + onChange={this._handleAffiliateAddressChange} + /> + </ConfigGeneratorSection> + <ConfigGeneratorSection + title="Fee percentage" + actionText="Learn more" + onActionTextClick={this._handleAffiliatePercentageLearnMoreClick} + > + <FeePercentageSlider + value={value.affiliateInfo.feePercentage} + onChange={this._handleAffiliatePercentageChange} + isDisabled={ + _.isUndefined(value.affiliateInfo) || + _.isUndefined(value.affiliateInfo.feeRecipient) || + _.isEmpty(value.affiliateInfo.feeRecipient) + } + /> + </ConfigGeneratorSection> + </Container> + ); + } + private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => { + if (_.isEmpty(this.state.availableTokens)) { + return { + title: 'What tokens can users buy?', + }; + } + if (_.isUndefined(this.props.value.availableAssetDatas)) { + return { + title: 'What tokens can users buy?', + actionText: 'Unselect All', + onActionTextClick: this._handleUnselectAllClick, + }; + } + return { + title: 'What tokens can users buy?', + actionText: 'Select All', + onActionTextClick: this._handleSelectAllClick, + }; + }; + private readonly _generateItems = (): SelectItemConfig[] => { + return _.map(SRA_ENDPOINTS, endpoint => ({ + label: endpoint, + value: endpoint, + onClick: this._handleSRASelection.bind(this, endpoint), + })); + }; + private readonly _handleAffiliatePercentageLearnMoreClick = (): void => { + window.open(`${WebsitePaths.Wiki}#Learn-About-Affiliate-Fees`, '_blank'); + }; + private readonly _handleSRASelection = (event: React.ChangeEvent<HTMLSelectElement>) => { + const sraEndpoint = event.target.value; + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + orderSource: sraEndpoint, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleAffiliateAddressChange = (address: string, isValid: boolean) => { + const oldConfig: ZeroExInstantBaseConfig = this.props.value; + const newConfig: ZeroExInstantBaseConfig = { + ...oldConfig, + affiliateInfo: { + feeRecipient: address, + feePercentage: oldConfig.affiliateInfo.feePercentage, + }, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleAffiliatePercentageChange = (value: number) => { + const oldConfig: ZeroExInstantBaseConfig = this.props.value; + const newConfig: ZeroExInstantBaseConfig = { + ...oldConfig, + affiliateInfo: { + feeRecipient: oldConfig.affiliateInfo.feeRecipient, + feePercentage: value, + }, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleSelectAllClick = () => { + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: undefined, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleUnselectAllClick = () => { + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: [], + }; + this.props.onConfigChange(newConfig); + }; + private readonly _handleTokenClick = (assetData: string) => { + const { value } = this.props; + let newAvailableAssetDatas: string[] = []; + const allKnownAssetDatas = _.keys(this.state.availableTokens); + const availableAssetDatas = value.availableAssetDatas; + if (_.isUndefined(availableAssetDatas)) { + // It being undefined means it's all tokens. + newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData); + } else if (!_.includes(availableAssetDatas, assetData)) { + // Add it + newAvailableAssetDatas = [...availableAssetDatas, assetData]; + if (newAvailableAssetDatas.length === allKnownAssetDatas.length) { + // If all tokens are manually selected, just show none. + newAvailableAssetDatas = undefined; + } + } else { + // Remove it + newAvailableAssetDatas = _.pull(availableAssetDatas, assetData); + } + const newConfig: ZeroExInstantBaseConfig = { + ...this.props.value, + availableAssetDatas: newAvailableAssetDatas, + }; + this.props.onConfigChange(newConfig); + }; + private readonly _setAvailableAssetsFromOrderProvider = async (): Promise<void> => { + const { value } = this.props; + if (!_.isUndefined(value.orderSource) && _.isString(value.orderSource)) { + this.setState({ isLoadingAvailableTokens: true }); + const networkId = constants.NETWORK_ID_MAINNET; + const sraOrderProvider = new StandardRelayerAPIOrderProvider(value.orderSource, networkId); + const etherTokenAddress = getContractAddressesForNetworkOrThrow(networkId).etherToken; + const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress); + const assetDatas = await sraOrderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData); + const availableTokens = _.reduce( + assetDatas, + (acc, assetData) => { + const metaDataIfExists = assetMetaDataMap[assetData] as ERC20AssetMetaData; + if (metaDataIfExists) { + acc[assetData] = metaDataIfExists; + } + return acc; + }, + {} as ObjectMap<ERC20AssetMetaData>, + ); + this.setState({ availableTokens, isLoadingAvailableTokens: false }); + } + }; + private readonly _renderTokenMultiSelectOrSpinner = (): React.ReactNode => { + const { value } = this.props; + const { availableTokens, isLoadingAvailableTokens } = this.state; + const multiSelectHeight = '200px'; + if (isLoadingAvailableTokens) { + return ( + <Container + className="flex flex-column items-center justify-center" + height={multiSelectHeight} + backgroundColor={colors.white} + borderRadius="4px" + width="100%" + > + <Container position="relative" left="12px" marginBottom="20px"> + <Spinner /> + </Container> + <Text fontSize="16px">Loading...</Text> + </Container> + ); + } + const availableAssetDatas = _.keys(availableTokens); + if (availableAssetDatas.length === 0) { + return ( + <Container + className="flex flex-column items-center justify-center" + height={multiSelectHeight} + backgroundColor={colors.white} + borderRadius="4px" + width="100%" + > + <Text fontSize="16px">No tokens available. Try another endpoint?</Text> + </Container> + ); + } + const items = _.map(_.keys(availableTokens), assetData => { + const metaData = availableTokens[assetData]; + return { + value: assetData, + renderItemContent: (isSelected: boolean) => ( + <Container className="flex items-center"> + <Container marginRight="10px"> + <CheckMark isChecked={isSelected} color={colors.brandLight} /> + </Container> + <CheckboxText isSelected={isSelected}> + {metaData.symbol.toUpperCase()} — {metaData.name} + </CheckboxText> + </Container> + ), + onClick: this._handleTokenClick.bind(this, assetData), + }; + }); + return <MultiSelect items={items} selectedValues={value.availableAssetDatas} height={multiSelectHeight} />; + }; +} + +export interface ConfigGeneratorSectionProps { + title: string; + actionText?: string; + onActionTextClick?: () => void; + isOptional?: boolean; + marginBottom?: string; +} + +export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = ({ + title, + actionText, + onActionTextClick, + isOptional, + marginBottom, + children, +}) => ( + <Container marginBottom={marginBottom}> + <Container marginBottom="10px" className="flex justify-between items-center"> + <Heading size="small" marginBottom="0" isFlex={true}> + <span>{title}</span> + {isOptional && <OptionalText> Optional</OptionalText>} + </Heading> + {actionText && <OptionalAction onClick={onActionTextClick}>{actionText}</OptionalAction>} + </Container> + {children} + </Container> +); + +ConfigGeneratorSection.defaultProps = { + marginBottom: '30px', +}; + +const OptionalText = styled.span` + display: inline; + font-size: 14px; + color: #999999; + flex-shrink: 0; +`; + +interface CheckboxTextProps { + isSelected?: boolean; +} + +const CheckboxText = + styled.span < + CheckboxTextProps > + ` + font-size: 14px; + line-height: 18px; + color: ${props => (props.isSelected ? colors.brandDark : '#666666')} +`; + +const OptionalAction = styled(OptionalText)` + cursor: pointer; +`; diff --git a/packages/website/ts/pages/instant/config_generator_address_input.tsx b/packages/website/ts/pages/instant/config_generator_address_input.tsx new file mode 100644 index 000000000..890e39da6 --- /dev/null +++ b/packages/website/ts/pages/instant/config_generator_address_input.tsx @@ -0,0 +1,84 @@ +import { addressUtils } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; +import styled from 'styled-components'; + +import { colors } from 'ts/style/colors'; + +import { Container } from 'ts/components/ui/container'; + +import { Paragraph } from 'ts/components/text'; + +export interface ConfigGeneratorAddressInputProps { + value?: string; + onChange?: (address: string, isValid: boolean) => void; +} + +export interface ConfigGeneratorAddressInputState { + errMsg: string; +} + +export interface InputProps { + className?: string; + value?: string; + width?: string; + fontSize?: string; + fontColor?: string; + padding?: string; + placeholderColor?: string; + placeholder?: string; + backgroundColor?: string; + onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; +} + +export class ConfigGeneratorAddressInput extends React.Component< + ConfigGeneratorAddressInputProps, + ConfigGeneratorAddressInputState +> { + public state = { + errMsg: '', + }; + public render(): React.ReactNode { + const { errMsg } = this.state; + const hasError = !_.isEmpty(errMsg); + return ( + <Container height="80px"> + <Input value={this.props.value} onChange={this._handleChange} placeholder="0xe99...aa8da4" /> + <Container marginTop="5px" isHidden={!hasError} height="25px"> + <Paragraph size="small" isNoMargin={true}> + {errMsg} + </Paragraph> + </Container> + </Container> + ); + } + + private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const address = event.target.value; + const isValidAddress = addressUtils.isAddress(address.toLowerCase()) || address === ''; + const errMsg = isValidAddress ? '' : 'Please enter a valid Ethereum address'; + this.setState({ + errMsg, + }); + this.props.onChange(address, isValidAddress); + }; +} + +const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => ( + <input className={className} value={value} onChange={onChange} placeholder={placeholder} /> +); + +export const Input = styled(PlainInput)` + background-color: ${colors.white}; + color: ${colors.textDarkSecondary}; + font-size: 1rem; + width: 100%; + padding: 16px 20px 18px; + border-radius: 4px; + border: 1px solid transparent; + outline: none; + &::placeholder { + color: #333333; + opacity: 0.5; + } +`; diff --git a/packages/website/ts/pages/instant/configurator.tsx b/packages/website/ts/pages/instant/configurator.tsx new file mode 100644 index 000000000..a63e1752e --- /dev/null +++ b/packages/website/ts/pages/instant/configurator.tsx @@ -0,0 +1,104 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import styled from 'styled-components'; + +import { CodeDemo } from 'ts/pages/instant/code_demo'; +import { ConfigGenerator } from 'ts/pages/instant/config_generator'; + +import { Link } from 'ts/components/link'; +import { Column, FlexWrap } from 'ts/components/newLayout'; +import { Heading } from 'ts/components/text'; +import { WebsitePaths } from 'ts/types'; + +import { ZeroExInstantBaseConfig } from '../../../../instant/src/types'; + +export interface ConfiguratorState { + instantConfig: ZeroExInstantBaseConfig; +} + +export class Configurator extends React.Component { + public state: ConfiguratorState = { + instantConfig: { + orderSource: 'https://api.radarrelay.com/0x/v2/', + availableAssetDatas: undefined, + affiliateInfo: { + feeRecipient: '', + feePercentage: 0, + }, + }, + }; + public render(): React.ReactNode { + const codeToDisplay = this._generateCodeDemoCode(); + return ( + <FlexWrap isFlex={true}> + <Column width="442px" padding="0 70px 0 0"> + <ConfigGenerator value={this.state.instantConfig} onConfigChange={this._handleConfigChange} /> + </Column> + <Column width="100%"> + <HeadingWrapper> + <Heading size="small" marginBottom="15px"> + Code Snippet + </Heading> + <Link href={`${WebsitePaths.Wiki}#Get-Started-With-Instant`} isBlock={true} target="_blank"> + Explore the Docs + </Link> + </HeadingWrapper> + <CodeDemo key={codeToDisplay}>{codeToDisplay}</CodeDemo> + </Column> + </FlexWrap> + ); + } + private readonly _handleConfigChange = (config: ZeroExInstantBaseConfig) => { + this.setState({ + instantConfig: config, + }); + }; + private readonly _generateCodeDemoCode = (): string => { + const { instantConfig } = this.state; + return `<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <script src="https://instant.0x.org/instant.js"></script> + </head> + <body> + <script> + zeroExInstant.render({ + orderSource: '${instantConfig.orderSource}',${ + !_.isUndefined(instantConfig.affiliateInfo) && instantConfig.affiliateInfo.feeRecipient + ? `\n affiliateInfo: { + feeRecipient: '${instantConfig.affiliateInfo.feeRecipient.toLowerCase()}', + feePercentage: ${instantConfig.affiliateInfo.feePercentage} + },` + : '' + }${ + !_.isUndefined(instantConfig.availableAssetDatas) + ? `\n availableAssetDatas: ${this._renderAvailableAssetDatasString( + instantConfig.availableAssetDatas, + )}` + : '' + } + }, 'body'); + </script> + </body> + </html>`; + }; + private readonly _renderAvailableAssetDatasString = (availableAssetDatas: string[]): string => { + const stringAvailableAssetDatas = availableAssetDatas.map(assetData => `'${assetData}'`); + if (availableAssetDatas.length < 2) { + return `[${stringAvailableAssetDatas.join(', ')}]`; + } + return `[\n ${stringAvailableAssetDatas.join( + ', \n ', + )}\n ]`; + }; +} + +const HeadingWrapper = styled.div` + display: flex; + justify-content: space-between; + + a { + transform: translateY(-8px); + } +`; diff --git a/packages/website/ts/pages/instant/fee_percentage_slider.tsx b/packages/website/ts/pages/instant/fee_percentage_slider.tsx new file mode 100644 index 000000000..c4d9f908f --- /dev/null +++ b/packages/website/ts/pages/instant/fee_percentage_slider.tsx @@ -0,0 +1,80 @@ +import Slider from 'rc-slider'; +import * as React from 'react'; +import styled from 'styled-components'; +import 'ts/pages/instant/rc-slider.css'; + +import { colors } from 'ts/style/colors'; + +const SliderWithTooltip = (Slider as any).createSliderWithTooltip(Slider); +// tslint:disable-next-line:no-unused-expression + +export interface FeePercentageSliderProps { + value: number; + isDisabled?: boolean; + onChange: (value: number) => void; +} + +export class FeePercentageSlider extends React.Component<FeePercentageSliderProps> { + public render(): React.ReactNode { + return ( + <StyledSlider + min={0} + max={0.05} + step={0.0025} + value={this.props.value} + disabled={this.props.isDisabled} + onChange={this.props.onChange} + tipFormatter={this._feePercentageSliderFormatter} + tipProps={{ placement: 'bottom', overlayStyle: { backgroundColor: '#fff', borderRadius: '4px' } }} + trackStyle={{ + backgroundColor: colors.brandLight, + }} + railStyle={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + }} + handleStyle={{ + border: 'none', + boxShadow: 'none', + }} + activeDotStyle={{ + boxShadow: 'none', + border: 'none', + }} + /> + ); + } + private readonly _feePercentageSliderFormatter = (value: number): React.ReactNode => { + return <Text>{`${(value * 100).toFixed(2)}%`}</Text>; + }; +} + +const StyledSlider = styled(SliderWithTooltip)` + .rc-slider-tooltip__inner { + box-shadow: none !important; + background-color: ${colors.white} !important; + border-radius: 4px !important; + padding: 3px 12px !important; + height: auto !important; + position: relative; + top: 7px; + &:after { + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-width: 6px; + bottom: 100%; + left: 100%; + border-bottom-color: ${colors.white}; + margin-left: -60%; + } + } +`; + +const Text = styled.span` + color: #000000; + font-size: 12px; + line-height: 18px; +`; diff --git a/packages/website/ts/pages/instant/rc-slider.css b/packages/website/ts/pages/instant/rc-slider.css new file mode 100644 index 000000000..63038324e --- /dev/null +++ b/packages/website/ts/pages/instant/rc-slider.css @@ -0,0 +1,295 @@ +.rc-slider { + position: relative; + height: 14px; + padding: 5px 0; + width: 100%; + border-radius: 6px; + -ms-touch-action: none; + touch-action: none; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.rc-slider * { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.rc-slider-rail { + position: absolute; + width: 100%; + background-color: #e9e9e9; + height: 4px; + border-radius: 6px; +} + +.rc-slider-track { + position: absolute; + left: 0; + height: 4px; + border-radius: 6px; + background-color: #abe2fb; +} + +.rc-slider-handle { + position: absolute; + margin-left: -7px; + margin-top: -5px; + width: 14px; + height: 14px; + cursor: pointer; + cursor: -webkit-grab; + cursor: grab; + border-radius: 50%; + border: solid 2px #96dbfa; + background-color: #fff; + -ms-touch-action: pan-x; + touch-action: pan-x; +} + +.rc-slider-handle:focus { + border-color: #57c5f7; + box-shadow: 0 0 0 5px #96dbfa; + outline: none; +} + +.rc-slider-handle-click-focused:focus { + border-color: #96dbfa; + box-shadow: unset; +} + +.rc-slider-handle:hover { + border-color: #57c5f7; +} + +.rc-slider-handle:active { + border-color: #57c5f7; + box-shadow: 0 0 5px #57c5f7; + cursor: -webkit-grabbing; + cursor: grabbing; +} + +.rc-slider-mark { + position: absolute; + top: 18px; + left: 0; + width: 100%; + font-size: 12px; +} + +.rc-slider-mark-text { + position: absolute; + display: inline-block; + vertical-align: middle; + text-align: center; + cursor: pointer; + color: #999; +} + +.rc-slider-mark-text-active { + color: #666; +} + +.rc-slider-step { + position: absolute; + width: 100%; + height: 4px; + background: transparent; +} + +.rc-slider-dot { + position: absolute; + bottom: -2px; + margin-left: -4px; + width: 8px; + height: 8px; + border: 2px solid #e9e9e9; + background-color: #fff; + cursor: pointer; + border-radius: 50%; + vertical-align: middle; +} + +.rc-slider-dot-active { + border-color: #96dbfa; +} + +.rc-slider-disabled { + opacity: 0.2; +} + +.rc-slider-disabled .rc-slider-track { + background-color: #ccc; +} + +.rc-slider-disabled .rc-slider-handle, +.rc-slider-disabled .rc-slider-dot { + border-color: #ccc; + box-shadow: none; + background-color: #fff; + cursor: not-allowed; +} + +.rc-slider-disabled .rc-slider-mark-text, +.rc-slider-disabled .rc-slider-dot { + cursor: not-allowed !important; +} + +.rc-slider-vertical { + width: 14px; + height: 100%; + padding: 0 5px; +} + +.rc-slider-vertical .rc-slider-rail { + height: 100%; + width: 4px; +} + +.rc-slider-vertical .rc-slider-track { + left: 5px; + bottom: 0; + width: 4px; +} + +.rc-slider-vertical .rc-slider-handle { + margin-left: -5px; + margin-bottom: -7px; + -ms-touch-action: pan-y; + touch-action: pan-y; +} + +.rc-slider-vertical .rc-slider-mark { + top: 0; + left: 18px; + height: 100%; +} + +.rc-slider-vertical .rc-slider-step { + height: 100%; + width: 4px; +} + +.rc-slider-vertical .rc-slider-dot { + left: 2px; + margin-bottom: -4px; +} + +.rc-slider-vertical .rc-slider-dot:first-child { + margin-bottom: -4px; +} + +.rc-slider-vertical .rc-slider-dot:last-child { + margin-bottom: -4px; +} + +.rc-slider-tooltip-zoom-down-enter, +.rc-slider-tooltip-zoom-down-appear { + animation-duration: .3s; + animation-fill-mode: both; + display: block !important; + animation-play-state: paused; +} + +.rc-slider-tooltip-zoom-down-leave { + animation-duration: .3s; + animation-fill-mode: both; + display: block !important; + animation-play-state: paused; +} + +.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active, +.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active { + animation-name: rcSliderTooltipZoomDownIn; + animation-play-state: running; +} + +.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active { + animation-name: rcSliderTooltipZoomDownOut; + animation-play-state: running; +} + +.rc-slider-tooltip-zoom-down-enter, +.rc-slider-tooltip-zoom-down-appear { + transform: scale(0, 0); + animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); +} + +.rc-slider-tooltip-zoom-down-leave { + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); +} + +@keyframes rcSliderTooltipZoomDownIn { + 0% { + opacity: 0; + transform-origin: 50% 100%; + transform: scale(0, 0); + } + + 100% { + transform-origin: 50% 100%; + transform: scale(1, 1); + } +} + +@keyframes rcSliderTooltipZoomDownOut { + 0% { + transform-origin: 50% 100%; + transform: scale(1, 1); + } + + 100% { + opacity: 0; + transform-origin: 50% 100%; + transform: scale(0, 0); + } +} + +.rc-slider-tooltip { + position: absolute; + left: -9999px; + top: -9999px; + visibility: visible; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.rc-slider-tooltip * { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.rc-slider-tooltip-hidden { + display: none; +} + +.rc-slider-tooltip-placement-top { + padding: 4px 0 8px 0; +} + +.rc-slider-tooltip-inner { + padding: 4px 6px 4px; + min-width: 24px; + height: 24px; + font-size: 12px; + line-height: 1; + color: #000; + text-align: center; + text-decoration: none; +} + +.rc-slider-tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow { + bottom: 4px; + left: 50%; + margin-left: -4px; + border-width: 4px 4px 0; + border-top-color: #6c6c6c; +} diff --git a/packages/website/ts/pages/instant/select.tsx b/packages/website/ts/pages/instant/select.tsx new file mode 100644 index 000000000..d4146cfb0 --- /dev/null +++ b/packages/website/ts/pages/instant/select.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import styled from 'styled-components'; + +export interface SelectItemConfig { + label: string; + value?: string; + onClick?: () => void; +} + +interface SelectProps { + value?: string; + id: string; + items: SelectItemConfig[]; + emptyText?: string; + onChange?: (ev: React.ChangeEvent<HTMLSelectElement>) => void; + shouldIncludeEmpty: boolean; +} + +export const Select: React.FunctionComponent<SelectProps> = ({ + value, + id, + items, + shouldIncludeEmpty, + emptyText, + onChange, +}) => { + return ( + <Container> + <StyledSelect id={id} onChange={onChange}> + {shouldIncludeEmpty && <option value="">{emptyText}</option>} + {items.map((item, index) => ( + <option + key={`${id}-item-${index}`} + value={item.value} + selected={item.value === value} + onClick={item.onClick} + > + {item.label} + </option> + ))} + </StyledSelect> + <Caret width="12" height="7" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M11 1L6 6 1 1" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> + </Caret> + </Container> + ); +}; + +Select.defaultProps = { + emptyText: 'Select...', + shouldIncludeEmpty: true, +}; + +const Container = styled.div` + background-color: #fff; + border-radius: 4px; + display: flex; + width: 100%; + position: relative; +`; + +const StyledSelect = styled.select` + appearance: none; + border: 0; + font-size: 1rem; + width: 100%; + padding: 20px 20px 20px 20px; +`; + +const Caret = styled.svg` + position: absolute; + right: 20px; + top: calc(50% - 4px); +`; diff --git a/packages/website/ts/pages/jobs/benefits.tsx b/packages/website/ts/pages/jobs/benefits.tsx deleted file mode 100644 index 563b72e63..000000000 --- a/packages/website/ts/pages/jobs/benefits.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; - -import { Circle } from 'ts/components/ui/circle'; -import { Container } from 'ts/components/ui/container'; -import { Image } from 'ts/components/ui/image'; -import { Text } from 'ts/components/ui/text'; -import { colors } from 'ts/style/colors'; -import { styled } from 'ts/style/theme'; -import { ScreenWidths } from 'ts/types'; -import { constants } from 'ts/utils/constants'; - -const BENEFITS = [ - 'Comprehensive insurance (medical, dental, and vision)', - 'Unlimited vacation (three weeks per year minimum)', - 'Meals and snacks provided in-office daily', - 'Flexible hours and liberal work-from-home-policy', - 'Supportive remote working environment', - 'Transportation, phone, and wellness expense', - 'Relocation assistance', - 'Optional team excursions (fully paid, often international)', - 'Competitive salary and cryptocurrency-based compensation', -]; - -interface Value { - iconSrc: string; - text: string; -} -const VALUES: Value[] = [ - { - iconSrc: 'images/jobs/heart-icon.svg', - text: 'Do the right thing', - }, - { - iconSrc: 'images/jobs/ship-icon.svg', - text: 'Consistently ship', - }, - { - iconSrc: 'images/jobs/calendar-icon.svg', - text: 'Focus on long term impact', - }, -]; - -export interface BenefitsProps { - screenWidth: ScreenWidths; -} - -export const Benefits = (props: BenefitsProps) => { - const isSmallScreen = props.screenWidth === ScreenWidths.Sm; - return ( - <Container className="flex flex-column items-center py4 px3" backgroundColor={colors.white}> - {!isSmallScreen ? ( - <Container className="flex" maxWidth="1200px"> - <BenefitsList /> - <Container marginLeft="120px"> - <ValuesList /> - </Container> - </Container> - ) : ( - <Container className="flex-column"> - <BenefitsList /> - <Container marginTop="50px"> - <ValuesList /> - </Container> - </Container> - )} - </Container> - ); -}; - -const Header: React.StatelessComponent = ({ children }) => ( - <Container marginBottom="30px"> - <Text fontFamily="Roboto Mono" fontSize="24px" fontColor={colors.black}> - {children} - </Text> - </Container> -); - -interface BenefitsListProps { - className?: string; -} -const PlainBenefitsList: React.StatelessComponent<BenefitsListProps> = ({ className }) => { - return ( - <Container className={className}> - <Header>Benefits</Header> - {_.map(BENEFITS, benefit => <BenefitItem key={benefit} description={benefit} />)} - </Container> - ); -}; -const BenefitsList = styled(PlainBenefitsList)` - transform: translateY(30px); -`; - -interface BenefitItemProps { - description: string; -} - -const BenefitItem: React.StatelessComponent<BenefitItemProps> = ({ description }) => ( - <Container marginBottom="15px"> - <div className="flex"> - <Circle className="flex-none pr2 pt1" diameter={8} fillColor={colors.black} /> - <div className="flex-auto"> - <Text fontSize="14px" lineHeight="24px"> - {description} - </Text> - </div> - </div> - </Container> -); - -interface ValuesListProps { - className?: string; -} -const PlainValuesList: React.StatelessComponent<ValuesListProps> = ({ className }) => { - return ( - <Container className={className} maxWidth="270px"> - <Header>Our Values</Header> - {_.map(VALUES, value => <ValueItem key={value.text} {...value} />)} - <Text fontSize="14px" lineHeight="26px"> - We care deeply about our culture and values, and encourage you to{' '} - <a - style={{ color: colors.mediumBlue, textDecoration: 'none' }} - target="_blank" - href={constants.URL_MISSION_AND_VALUES_BLOG_POST} - > - read more on our blog - </a>. - </Text> - </Container> - ); -}; - -const ValuesList = styled(PlainValuesList)` - border-color: ${colors.beigeWhite}; - border-radius: 7px; - border-width: 1px; - border-style: solid; - padding-left: 38px; - padding-right: 38px; - padding-top: 28px; - padding-bottom: 28px; -`; - -type ValueItemProps = Value; -const ValueItem: React.StatelessComponent<ValueItemProps> = ({ iconSrc, text }) => { - return ( - <Container marginBottom="25px"> - <div className="flex items-center"> - <Image className="flex-none pr2" width="20px" src={iconSrc} /> - <div className="flex-auto"> - <Text fontSize="14px" lineHeight="24px" fontWeight="bold"> - {text} - </Text> - </div> - </div> - </Container> - ); -}; diff --git a/packages/website/ts/pages/jobs/jobs.tsx b/packages/website/ts/pages/jobs/jobs.tsx deleted file mode 100644 index 728e17f9e..000000000 --- a/packages/website/ts/pages/jobs/jobs.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { colors, utils as sharedUtils } from '@0x/react-shared'; -import * as _ from 'lodash'; -import * as React from 'react'; -import * as DocumentTitle from 'react-document-title'; - -import { Footer } from 'ts/components/footer'; -import { MetaTags } from 'ts/components/meta_tags'; -import { TopBar } from 'ts/components/top_bar/top_bar'; -import { Container } from 'ts/components/ui/container'; -import { Benefits } from 'ts/pages/jobs/benefits'; -import { Join0x } from 'ts/pages/jobs/join_0x'; -import { Mission } from 'ts/pages/jobs/mission'; -import { OpenPositions } from 'ts/pages/jobs/open_positions'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; -import { utils } from 'ts/utils/utils'; - -const OPEN_POSITIONS_HASH = 'positions'; -const THROTTLE_TIMEOUT = 100; -const DOCUMENT_TITLE = 'Careers at 0x'; -const DOCUMENT_DESCRIPTION = 'Join 0x in creating a tokenized world where all value can flow freely'; - -export interface JobsProps { - location: Location; - translate: Translate; - dispatcher: Dispatcher; - screenWidth: ScreenWidths; -} - -export interface JobsState {} - -export class Jobs extends React.Component<JobsProps, JobsState> { - // TODO: consolidate this small screen scaffolding into one place (its being used in portal and docs as well) - private readonly _throttledScreenWidthUpdate: () => void; - public constructor(props: JobsProps) { - super(props); - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - } - public componentDidMount(): void { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public render(): React.ReactNode { - return ( - <Container overflowX="hidden"> - <MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} /> - <DocumentTitle title={DOCUMENT_TITLE} /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - style={{ backgroundColor: colors.white, position: 'relative' }} - translate={this.props.translate} - /> - <Join0x onCallToActionClick={this._onJoin0xCallToActionClick.bind(this)} /> - <Mission screenWidth={this.props.screenWidth} /> - <Benefits screenWidth={this.props.screenWidth} /> - <OpenPositions hash={OPEN_POSITIONS_HASH} screenWidth={this.props.screenWidth} /> - <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> - </Container> - ); - } - private _onJoin0xCallToActionClick(): void { - sharedUtils.setUrlHash(OPEN_POSITIONS_HASH); - sharedUtils.scrollToHash(OPEN_POSITIONS_HASH, ''); - } - private _updateScreenWidth(): void { - const newScreenWidth = utils.getScreenWidth(); - this.props.dispatcher.updateScreenWidth(newScreenWidth); - } -} diff --git a/packages/website/ts/pages/jobs/join_0x.tsx b/packages/website/ts/pages/jobs/join_0x.tsx deleted file mode 100644 index e869cd455..000000000 --- a/packages/website/ts/pages/jobs/join_0x.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { colors } from '@0x/react-shared'; - -import * as React from 'react'; - -import { Button } from 'ts/components/ui/button'; -import { Container } from 'ts/components/ui/container'; -import { Image } from 'ts/components/ui/image'; -import { Text } from 'ts/components/ui/text'; -import { constants } from 'ts/utils/constants'; - -const BUTTON_TEXT = 'View open positions'; - -export interface Join0xProps { - onCallToActionClick: () => void; -} - -export const Join0x = (props: Join0xProps) => ( - <div className="clearfix center lg-py4 md-py4" style={{ backgroundColor: colors.white, color: colors.black }}> - <div - className="mx-auto inline-block align-middle py4" - style={{ lineHeight: '44px', textAlign: 'center', position: 'relative' }} - > - <Container className="sm-hide xs-hide" position="absolute" left="100%" marginLeft="80px"> - <Image src="images/jobs/hero-dots-right.svg" width="400px" /> - </Container> - <Container className="sm-hide xs-hide" position="absolute" right="100%" marginRight="80px"> - <Image src="images/jobs/hero-dots-left.svg" width="400px" /> - </Container> - <div className="h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> - Join Us in Our Mission - </div> - <Container className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 sm-center" maxWidth="537px"> - <Text fontSize="14px" lineHeight="30px"> - At 0x, our mission is to create a tokenized world where all value can flow freely. - <br /> - <br />We are powering a growing ecosystem of decentralized applications and solving novel challenges - to make our technology intuitive, flexible, and accessible to all.{' '} - <a - style={{ color: colors.mediumBlue, textDecoration: 'none' }} - target="_blank" - href={constants.URL_MISSION_AND_VALUES_BLOG_POST} - > - Read more - </a>{' '} - about our mission, and join us in building financial infrastructure upon which the exchange of - anything of value will take place. - </Text> - </Container> - <div className="py3"> - <Button - type="button" - backgroundColor={colors.black} - width="290px" - fontColor={colors.white} - fontSize="18px" - fontFamily="Roboto Mono" - onClick={props.onCallToActionClick} - > - {BUTTON_TEXT} - </Button> - </div> - </div> - </div> -); diff --git a/packages/website/ts/pages/jobs/mission.tsx b/packages/website/ts/pages/jobs/mission.tsx deleted file mode 100644 index 28546f985..000000000 --- a/packages/website/ts/pages/jobs/mission.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react'; - -import { Container } from 'ts/components/ui/container'; -import { Image } from 'ts/components/ui/image'; -import { Text } from 'ts/components/ui/text'; -import { colors } from 'ts/style/colors'; -import { ScreenWidths } from 'ts/types'; - -export interface MissionProps { - screenWidth: ScreenWidths; -} -export const Mission = (props: MissionProps) => { - const shouldShowImage = props.screenWidth === ScreenWidths.Lg; - const image = <Image src="/images/jobs/world-map.svg" maxWidth="500px" maxHeight="280px" />; - const missionStatementClassName = !shouldShowImage ? 'center' : undefined; - const missionStatement = ( - <Container className={missionStatementClassName} maxWidth="388px"> - <Text fontFamily="Roboto Mono" fontSize="22px" lineHeight="31px"> - Powered by a Diverse<br />Worldwide Community - </Text> - <Container marginTop="32px"> - <Text fontSize="14px" lineHeight="2em"> - We're a highly technical team with varied backgrounds in engineering, science, business, finance, - and research. While the core team is headquartered in San Francisco, there are 30+ teams building on - 0x and hundreds of thousands of participants behind our efforts globally. We're passionate about - open-source software and decentralized technology's potential to act as an equalizing force in the - world. - </Text> - </Container> - </Container> - ); - return ( - <div - className="flex flex-column items-center py4 px3" - style={{ backgroundColor: colors.jobsPageBackground, color: colors.black }} - > - {shouldShowImage ? ( - <Container className="flex items-center" maxWidth="1200px"> - {image} - <Container marginLeft="115px">{missionStatement}</Container> - </Container> - ) : ( - <Container className="flex flex-column items-center">{missionStatement}</Container> - )} - </div> - ); -}; diff --git a/packages/website/ts/pages/jobs/open_positions.tsx b/packages/website/ts/pages/jobs/open_positions.tsx deleted file mode 100644 index b8442a9c4..000000000 --- a/packages/website/ts/pages/jobs/open_positions.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import * as _ from 'lodash'; -import CircularProgress from 'material-ui/CircularProgress'; -import * as React from 'react'; - -import { Container } from 'ts/components/ui/container'; -import { Retry } from 'ts/components/ui/retry'; -import { Text } from 'ts/components/ui/text'; -import { colors } from 'ts/style/colors'; -import { styled } from 'ts/style/theme'; -import { ScreenWidths, WebsiteBackendJobInfo } from 'ts/types'; -import { backendClient } from 'ts/utils/backend_client'; -import { constants } from 'ts/utils/constants'; -import { utils } from 'ts/utils/utils'; - -const TABLE_ROW_MIN_HEIGHT = 100; - -export interface OpenPositionsProps { - hash: string; - screenWidth: ScreenWidths; -} -export interface OpenPositionsState { - jobInfos?: WebsiteBackendJobInfo[]; - error?: Error; -} - -export class OpenPositions extends React.Component<OpenPositionsProps, OpenPositionsState> { - private _isUnmounted: boolean; - constructor(props: OpenPositionsProps) { - super(props); - this._isUnmounted = false; - this.state = { - jobInfos: undefined, - error: undefined, - }; - } - public componentWillMount(): void { - // tslint:disable-next-line:no-floating-promises - this._fetchJobInfosAsync(); - } - public componentWillUnmount(): void { - this._isUnmounted = true; - } - public render(): React.ReactNode { - const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.jobInfos); - const isSmallScreen = utils.isMobileWidth(this.props.screenWidth); - return ( - <Container id={this.props.hash} className="mx-auto pb4 px3 max-width-4"> - {!isSmallScreen && ( - <hr style={{ border: 0, borderTop: 1, borderStyle: 'solid', borderColor: colors.beigeWhite }} /> - )} - <Container marginTop="64px" marginBottom="50px"> - <Text fontFamily="Roboto Mono" fontSize="24px" fontColor={colors.black}> - Open Positions - </Text> - </Container> - {isReadyToRender ? this._renderTable() : this._renderLoading()} - </Container> - ); - } - private _renderLoading(): React.ReactNode { - return ( - // TODO: consolidate this loading component with the one in portal and RelayerIndex - // TODO: possibly refactor into a generic loading container with spinner and retry UI - <div className="center"> - {_.isUndefined(this.state.error) ? ( - <CircularProgress size={40} thickness={5} /> - ) : ( - <Retry onRetry={this._fetchJobInfosAsync.bind(this)} /> - )} - </div> - ); - } - private _renderTable(): React.ReactNode { - return ( - <Container width="100%"> - <div> - {_.map(this.state.jobInfos, jobInfo => { - return ( - <JobInfoTableRow - key={jobInfo.id} - screenWidth={this.props.screenWidth} - jobInfo={jobInfo} - onClick={this._openJobInfoUrl.bind(this, jobInfo)} - /> - ); - })} - </div> - <Container className="center" marginTop="70px"> - <Text fontStyle="italic" fontSize="14px"> - Interested in telling us why you'd be a valuable addition to the team outside of the positions - listed above?{' '} - <a - style={{ color: colors.mediumBlue, textDecoration: 'none' }} - href={`mailto:${constants.EMAIL_JOBS}`} - > - Email us! - </a> - </Text> - </Container> - </Container> - ); - } - private async _fetchJobInfosAsync(): Promise<void> { - try { - if (!this._isUnmounted) { - this.setState({ - jobInfos: undefined, - error: undefined, - }); - } - const jobInfos = await backendClient.getJobInfosAsync(); - if (!this._isUnmounted) { - this.setState({ - jobInfos, - }); - } - } catch (error) { - if (!this._isUnmounted) { - this.setState({ - error, - }); - } - } - } - private _openJobInfoUrl(jobInfo: WebsiteBackendJobInfo): void { - const url = jobInfo.url; - utils.openUrl(url); - } -} - -export interface JobInfoTableRowProps { - className?: string; - screenWidth: ScreenWidths; - jobInfo: WebsiteBackendJobInfo; - onClick?: (event: React.MouseEvent<HTMLElement>) => void; -} - -const PlainJobInfoTableRow: React.StatelessComponent<JobInfoTableRowProps> = ({ - className, - screenWidth, - jobInfo, - onClick, -}) => { - const isSmallScreen = screenWidth === ScreenWidths.Sm; - const titleClassName = isSmallScreen ? 'col col-12 center' : 'col col-5'; - const paddingLeft = isSmallScreen ? undefined : '30px'; - return ( - <Container className={className} onClick={onClick} marginBottom="30px" paddingLeft={paddingLeft}> - <Container className="flex items-center" minHeight={TABLE_ROW_MIN_HEIGHT} width="100%"> - <Container className="clearfix container" width="100%"> - <Container className={titleClassName}> - <Text fontSize="16px" fontWeight="bold" fontColor={colors.mediumBlue}> - {jobInfo.title} - </Text> - </Container> - {!isSmallScreen && ( - <Container className="col col-3"> - <Text fontSize="16px">{jobInfo.department}</Text> - </Container> - )} - {!isSmallScreen && ( - <Container className="col col-4 center"> - <Text fontSize="16px">{jobInfo.office}</Text> - </Container> - )} - </Container> - </Container> - </Container> - ); -}; - -export const JobInfoTableRow = styled(PlainJobInfoTableRow)` - cursor: pointer; - background-color: ${colors.grey100}; - border-radius: 7px; - &:hover { - opacity: 0.5; - } -`; diff --git a/packages/website/ts/pages/jobs/photo_rail.tsx b/packages/website/ts/pages/jobs/photo_rail.tsx deleted file mode 100644 index acc9dcb91..000000000 --- a/packages/website/ts/pages/jobs/photo_rail.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; - -import { FilledImage } from 'ts/components/ui/filled_image'; - -export interface PhotoRailProps { - images: string[]; -} - -export const PhotoRail = (props: PhotoRailProps) => { - return ( - <div className="clearfix" style={{ height: 490 }}> - {_.map(props.images, (image: string) => { - return ( - <div key={image} className="col lg-col-4 md-col-4 col-12 center" style={{ height: '100%' }}> - <FilledImage src={image} /> - </div> - ); - })} - </div> - ); -}; diff --git a/packages/website/ts/pages/landing.tsx b/packages/website/ts/pages/landing.tsx new file mode 100644 index 000000000..b47d34dce --- /dev/null +++ b/packages/website/ts/pages/landing.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; + +import { SectionLandingAbout } from 'ts/components/sections/landing/about'; +import { SectionLandingClients } from 'ts/components/sections/landing/clients'; +import { SectionLandingCta } from 'ts/components/sections/landing/cta'; +import { SectionLandingHero } from 'ts/components/sections/landing/hero'; +import { SiteWrap } from 'ts/components/siteWrap'; + +import { ModalContact } from 'ts/components/modals/modal_contact'; + +interface Props { + theme: { + bgColor: string; + textColor: string; + linkColor: string; + }; +} + +export class NextLanding extends React.Component<Props> { + public state = { + isContactModalOpen: false, + }; + public render(): React.ReactNode { + return ( + <SiteWrap theme="dark"> + <DocumentTitle title="0x: The protocol for trading tokens on Ethereum" /> + <SectionLandingHero /> + <SectionLandingAbout /> + <SectionLandingClients /> + <SectionLandingCta onContactClick={this._onOpenContactModal} /> + <ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} /> + </SiteWrap> + ); + } + + public _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + public _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; +} diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx deleted file mode 100644 index bb76efe21..000000000 --- a/packages/website/ts/pages/landing/landing.tsx +++ /dev/null @@ -1,620 +0,0 @@ -import { colors, Link } from '@0x/react-shared'; -import * as _ from 'lodash'; -import * as React from 'react'; -import DocumentTitle from 'react-document-title'; -import { Footer } from 'ts/components/footer'; -import { SubscribeForm } from 'ts/components/forms/subscribe_form'; -import { TopBar } from 'ts/components/top_bar/top_bar'; -import { CallToAction } from 'ts/components/ui/button'; -import { Container } from 'ts/components/ui/container'; -import { Image } from 'ts/components/ui/image'; -import { Text } from 'ts/components/ui/text'; -import { TypedText } from 'ts/components/ui/typed_text'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; -import { constants } from 'ts/utils/constants'; -import { Translate } from 'ts/utils/translate'; -import { utils } from 'ts/utils/utils'; - -interface BoxContent { - title: string; - description: string; - imageUrl: string; - classNames: string; - maxWidth: number; -} -interface UseCase { - imageUrl: string; - type: string; - description: string; - classNames: string; - style?: React.CSSProperties; -} -interface Project { - logoFileName: string; - projectUrl: string; -} - -const THROTTLE_TIMEOUT = 100; -const WHATS_NEW_TITLE = 'Introducing the 0x Launch Kit'; -const WHATS_NEW_URL = 'https://blog.0xproject.com/introducing-the-0x-launch-kit-4acdc3453585'; -const TITLE_STYLE: React.CSSProperties = { - fontFamily: 'Roboto Mono', - color: colors.grey, - fontWeight: 300, - letterSpacing: 3, -}; -const ROTATING_LIST = [ - 'tokens', - 'game items', - 'digital art', - 'futures', - 'stocks', - 'derivatives', - 'loans', - 'cats', - 'everything', -]; - -const relayerProjects: Project[] = [ - { - logoFileName: 'ethfinex.png', - projectUrl: constants.PROJECT_URL_ETHFINEX, - }, - { - logoFileName: 'radar_relay.png', - projectUrl: constants.PROJECT_URL_RADAR_RELAY, - }, - { - logoFileName: 'paradex.png', - projectUrl: constants.PROJECT_URL_PARADEX, - }, - { - logoFileName: 'the_ocean.png', - projectUrl: constants.PROJECT_URL_0CEAN, - }, - { - logoFileName: 'amadeus.png', - projectUrl: constants.PROJECT_URL_AMADEUS, - }, - { - logoFileName: 'ddex.png', - projectUrl: constants.PROJECT_URL_DDEX, - }, - { - logoFileName: 'decent_ex.png', - projectUrl: constants.PROJECT_URL_DECENT_EX, - }, - { - logoFileName: 'dextroid.png', - projectUrl: constants.PROJECT_URL_DEXTROID, - }, - { - logoFileName: 'ercdex.png', - projectUrl: constants.PROJECT_URL_ERC_DEX, - }, - { - logoFileName: 'open_relay.png', - projectUrl: constants.PROJECT_URL_OPEN_RELAY, - }, - { - logoFileName: 'idt.png', - projectUrl: constants.PROJECT_URL_IDT, - }, - { - logoFileName: 'imtoken.png', - projectUrl: constants.PROJECT_URL_IMTOKEN, - }, -]; - -export interface LandingProps { - location: Location; - translate: Translate; - dispatcher: Dispatcher; -} - -interface LandingState { - screenWidth: ScreenWidths; -} - -export class Landing extends React.Component<LandingProps, LandingState> { - private readonly _throttledScreenWidthUpdate: () => void; - constructor(props: LandingProps) { - super(props); - this.state = { - screenWidth: utils.getScreenWidth(), - }; - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - } - public componentDidMount(): void { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillUnmount(): void { - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - } - public render(): React.ReactNode { - return ( - <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> - <DocumentTitle title="0x Protocol" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - isNightVersion={true} - style={{ backgroundColor: colors.heroGrey, position: 'relative' }} - translate={this.props.translate} - /> - {this._renderHero()} - {this._renderProjects( - relayerProjects, - this.props.translate.get(Key.RelayersHeader, Deco.Upper), - colors.projectsGrey, - true, - )} - {this._renderInfoBoxes()} - {this._renderTokenizationSection()} - {this._renderUseCases()} - {this._renderCallToAction()} - <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> - </div> - ); - } - private _renderHero(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const left = 'col lg-col-6 md-col-6 col-12 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; - const flexClassName = isSmallScreen - ? 'flex items-center flex-column justify-center' - : 'flex items-center justify-center'; - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 clearfix"> - {this._renderWhatsNew()} - <div className={`${flexClassName} lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-mt4 md-mt4 sm-mt2 sm-mb4`}> - <Container marginTop="30px" marginBottom="30px" marginLeft="15px" marginRight="15px"> - <Image src="/images/landing/0x_homepage.svg" maxWidth="100%" height="auto" /> - </Container> - <div className={left} style={{ color: colors.white, height: 390, lineHeight: '390px' }}> - <div - className="inline-block lg-align-middle md-align-middle sm-align-top" - style={{ - paddingLeft: isSmallScreen ? 0 : 12, - lineHeight: '36px', - }} - > - <Text - className="sm-pb2" - fontFamily="Roboto" - display="inline-block" - fontColor={colors.grey300} - fontWeight={500} - lineHeight="1.3em" - fontSize={isSmallScreen ? '28px' : '36px'} - > - {this.props.translate.get(Key.TopHeader, Deco.Cap)} - {this.props.translate.getLanguage() === Language.English && ( - <React.Fragment> - {' '} - for{' '} - <TypedText - fontFamily="Roboto" - display="inline-block" - fontColor={colors.white} - fontWeight={700} - lineHeight="1.3em" - fontSize={isSmallScreen ? '28px' : '36px'} - textList={ROTATING_LIST} - shouldRepeat={true} - /> - </React.Fragment> - )} - </Text> - <Container - className={`pt3 flex clearfix sm-mx-auto ${isSmallScreen ? 'justify-center' : ''}`} - > - <Container paddingRight="20px"> - <Link to={WebsitePaths.Docs}> - <CallToAction type="light"> - {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - </CallToAction> - </Link> - </Container> - <div> - <Link to={WebsitePaths.Portal}> - <CallToAction> - {this.props.translate.get(Key.TradeCallToAction, Deco.Cap)} - </CallToAction> - </Link> - </div> - </Container> - </div> - </div> - </div> - </div> - {this.props.translate.getLanguage() === Language.English && <SubscribeForm />} - </div> - ); - } - private _renderWhatsNew(): React.ReactNode { - return ( - <div className="sm-center sm-px1"> - <a href={WHATS_NEW_URL} target="_blank" className="inline-block text-decoration-none"> - <div className="flex items-center sm-pl0 md-pl2 lg-pl0"> - <Container - paddingTop="3px" - paddingLeft="8px" - paddingBottom="3px" - paddingRight="8px" - backgroundColor={colors.white} - borderRadius={6} - > - <Text fontSize="14px" fontWeight={500} fontColor={colors.heroGrey}> - New - </Text> - </Container> - <Container marginLeft="12px"> - <Text fontSize="16px" fontWeight={500} fontColor={colors.grey300}> - {WHATS_NEW_TITLE} - </Text> - </Container> - </div> - </a> - </div> - ); - } - private _renderProjects( - projects: Project[], - title: string, - backgroundColor: string, - isTitleCenter: boolean, - ): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const projectList = _.map(projects, (project: Project, i: number) => { - const isRelayersOnly = projects.length === 12; - let colWidth: number; - switch (this.state.screenWidth) { - case ScreenWidths.Sm: - colWidth = 4; - break; - - case ScreenWidths.Md: - colWidth = 3; - break; - - case ScreenWidths.Lg: - colWidth = isRelayersOnly ? 2 : 2 - i % 2; - break; - - default: - throw new Error(`Encountered unknown ScreenWidths value: ${this.state.screenWidth}`); - } - return ( - <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> - <div> - <a href={project.projectUrl} target="_blank" className="text-decoration-none"> - <img - src={`/images/landing/project_logos/${project.logoFileName}`} - height={isSmallScreen ? 60 : 92} - /> - </a> - </div> - </div> - ); - }); - return ( - <div className={`clearfix py4 ${isTitleCenter && 'center'}`} style={{ backgroundColor }}> - <div className="mx-auto max-width-4 clearfix sm-px3"> - <div className="h4 pb3 lg-pl0 md-pl3 sm-pl2" style={TITLE_STYLE}> - {title} - </div> - <div className="clearfix">{projectList}</div> - <div - className="pt3 mx-auto center" - style={{ - color: colors.landingLinkGrey, - fontFamily: 'Roboto Mono', - maxWidth: 300, - fontSize: 14, - }} - > - {this.props.translate.get(Key.FullListPrompt)}{' '} - <Link to={WebsitePaths.Portal} textDecoration="underline" fontColor={colors.landingLinkGrey}> - {this.props.translate.get(Key.FullListLink)} - </Link> - </div> - </div> - </div> - ); - } - private _renderTokenizationSection(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> - <div className="mx-auto max-width-4 py4 clearfix"> - {isSmallScreen && this._renderTokenCloud()} - <div - className="col lg-col-6 md-col-6 col-12 center" - style={{ color: colors.darkestGrey, height: 364, lineHeight: '364px' }} - > - <div - className="mx-auto inline-block lg-align-middle md-align-middle sm-align-top" - style={{ maxWidth: 385, lineHeight: '44px', textAlign: 'left' }} - > - <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> - {this.props.translate.get(Key.TokenizedSectionHeader, Deco.Cap)} - </div> - <div - className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" - style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7, maxWidth: 370 }} - > - {this.props.translate.get(Key.TokenizedSectionDescription, Deco.Cap)} - </div> - <div className="flex pt1 sm-px3">{this._renderMissionAndValuesButton()}</div> - </div> - </div> - {!isSmallScreen && this._renderTokenCloud()} - </div> - </div> - ); - } - private _renderTokenCloud(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="col lg-col-6 md-col-6 col-12 center"> - <img src="/images/landing/tokenized_world.png" height={isSmallScreen ? 280 : 364.5} /> - </div> - ); - } - private _renderMissionAndValuesButton(): React.ReactNode { - return ( - <a - href={constants.URL_MISSION_AND_VALUES_BLOG_POST} - target="_blank" - className="inline-block text-decoration-none" - > - <CallToAction>{this.props.translate.get(Key.OurMissionAndValues, Deco.CapWords)}</CallToAction> - </a> - ); - } - private _renderInfoBoxes(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const boxStyle: React.CSSProperties = { - maxWidth: 253, - height: 402, - backgroundColor: colors.grey50, - borderRadius: 5, - padding: '10px 24px 24px', - }; - const boxContents: BoxContent[] = [ - { - title: this.props.translate.get(Key.BenefitOneTitle, Deco.Cap), - description: this.props.translate.get(Key.BenefitOneDescription, Deco.Cap), - imageUrl: '/images/landing/distributed_network.png', - classNames: '', - maxWidth: 160, - }, - { - title: this.props.translate.get(Key.BenefitTwoTitle, Deco.Cap), - description: this.props.translate.get(Key.BenefitTwoDescription, Deco.Cap), - imageUrl: '/images/landing/liquidity.png', - classNames: 'mx-auto', - maxWidth: 160, - }, - { - title: this.props.translate.get(Key.BenefitThreeTitle, Deco.Cap), - description: this.props.translate.get(Key.BenefitThreeDescription, Deco.Cap), - imageUrl: '/images/landing/exchange_everywhere.png', - classNames: 'right', - maxWidth: 130, - }, - ]; - const boxes = _.map(boxContents, (boxContent: BoxContent) => { - return ( - <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> - <div className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`} style={boxStyle}> - <Container className="flex items-center" height="210px"> - <img - className="mx-auto" - src={boxContent.imageUrl} - style={{ height: 'auto', maxWidth: boxContent.maxWidth }} - /> - </Container> - <div className="h3" style={{ color: 'black', fontFamily: 'Roboto Mono' }}> - {boxContent.title} - </div> - <div className="pt2 pb2" style={{ fontFamily: 'Roboto Mono', fontSize: 14 }}> - {boxContent.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> - <div className="center pb3 pt4" style={TITLE_STYLE}> - {this.props.translate.get(Key.BenefitsHeader, Deco.Upper)} - </div> - <div className="mx-auto pb4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> - {boxes} - </div> - </div> - ); - } - private _getUseCases(): UseCase[] { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const isEnglish = this.props.translate.getLanguage() === Language.English; - if (isEnglish) { - return [ - { - imageUrl: '/images/landing/governance_icon.png', - type: this.props.translate.get(Key.GamingAndCollectables, Deco.Upper), - description: this.props.translate.get(Key.GamingAndCollectablesDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/prediction_market_icon.png', - type: this.props.translate.get(Key.PredictionMarkets, Deco.Upper), - description: this.props.translate.get(Key.PredictionMarketsDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/fund_management_icon.png', - type: this.props.translate.get(Key.OrderBooks, Deco.Upper), - description: this.props.translate.get(Key.OrderBooksDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/loans_icon.png', - type: this.props.translate.get(Key.DecentralizedLoans, Deco.Upper), - description: this.props.translate.get(Key.DecentralizedLoansDescription, Deco.Cap), - classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', - style: { - width: 291, - float: 'right', - marginTop: !isSmallScreen ? 38 : 0, - }, - }, - { - imageUrl: '/images/landing/stable_tokens_icon.png', - type: this.props.translate.get(Key.StableTokens, Deco.Upper), - description: this.props.translate.get(Key.StableTokensDescription, Deco.Cap), - classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', - style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, - }, - ]; - } else { - return [ - { - imageUrl: '/images/landing/governance_icon.png', - type: this.props.translate.get(Key.DecentralizedGovernance, Deco.Upper), - description: this.props.translate.get(Key.DecentralizedGovernanceDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/prediction_market_icon.png', - type: this.props.translate.get(Key.PredictionMarkets, Deco.Upper), - description: this.props.translate.get(Key.PredictionMarketsDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/stable_tokens_icon.png', - type: this.props.translate.get(Key.StableTokens, Deco.Upper), - description: this.props.translate.get(Key.StableTokensDescription, Deco.Cap), - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/loans_icon.png', - type: this.props.translate.get(Key.DecentralizedLoans, Deco.Upper), - description: this.props.translate.get(Key.DecentralizedLoansDescription, Deco.Cap), - classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', - style: { - width: 291, - float: 'right', - marginTop: !isSmallScreen ? 38 : 0, - }, - }, - { - imageUrl: '/images/landing/fund_management_icon.png', - type: this.props.translate.get(Key.FundManagement, Deco.Upper), - description: this.props.translate.get(Key.FundManagementDescription, Deco.Cap), - classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', - style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, - }, - ]; - } - } - private _renderUseCases(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const useCases = this._getUseCases(); - const cases = _.map(useCases, (useCase: UseCase) => { - const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; - const useCaseBoxStyle = { - color: colors.grey, - border: `1px solid ${colors.grey750}`, - borderRadius: 4, - maxWidth: isSmallScreen ? 375 : 'none', - ...style, - }; - const typeStyle: React.CSSProperties = { - color: colors.lightGrey, - fontSize: 13, - textTransform: 'uppercase', - fontFamily: 'Roboto Mono', - fontWeight: 300, - }; - return ( - <div - key={`useCase-${useCase.type}`} - className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`} - > - <div className="relative p2 pb2 sm-mx-auto" style={useCaseBoxStyle}> - <div className="absolute center" style={{ top: -35, width: 'calc(100% - 32px)' }}> - <img src={useCase.imageUrl} style={{ height: 50 }} /> - </div> - <div className="pt2 center" style={typeStyle}> - {useCase.type} - </div> - <div - className="pt2" - style={{ - lineHeight: 1.5, - fontSize: 14, - overflow: 'hidden', - height: 124, - }} - > - {useCase.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> - <div className="center h4 pb3 lg-pl0 md-pl3 sm-pl2" style={TITLE_STYLE}> - {this.props.translate.get(Key.UseCasesHeader, Deco.Upper)} - </div> - <div className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix" style={{ maxWidth: '67em' }}> - {cases} - </div> - </div> - ); - } - private _renderCallToAction(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const callToActionClassNames = - 'lg-pr3 md-pr3 lg-right-align md-right-align sm-center sm-px3 h4 lg-table-cell md-table-cell'; - return ( - <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 pb4 mb3 clearfix center"> - <div className="center inline-block" style={{ textAlign: 'left' }}> - <div - className={callToActionClassNames} - style={{ - fontFamily: 'Roboto Mono', - color: colors.white, - lineHeight: isSmallScreen ? 1.7 : 3, - }} - > - {this.props.translate.get(Key.FinalCallToAction, Deco.Cap)} - </div> - <div className="sm-center sm-pt2 lg-table-cell md-table-cell"> - <Link to={WebsitePaths.Docs}> - <CallToAction fontSize="15px"> - {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - </CallToAction> - </Link> - </div> - </div> - </div> - </div> - ); - } - private _updateScreenWidth(): void { - const newScreenWidth = utils.getScreenWidth(); - if (newScreenWidth !== this.state.screenWidth) { - this.setState({ - screenWidth: newScreenWidth, - }); - } - } -} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/launch_kit.tsx b/packages/website/ts/pages/launch_kit.tsx new file mode 100644 index 000000000..dd4de4d99 --- /dev/null +++ b/packages/website/ts/pages/launch_kit.tsx @@ -0,0 +1,125 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; + +import { Hero } from 'ts/components/hero'; + +import { Banner } from 'ts/components/banner'; +import { Button } from 'ts/components/button'; +import { Definition } from 'ts/components/definition'; +import { Icon } from 'ts/components/icon'; +import { SiteWrap } from 'ts/components/siteWrap'; + +import { Section } from 'ts/components/newLayout'; +import { constants } from 'ts/utils/constants'; + +import { ModalContact } from '../components/modals/modal_contact'; + +const offersData = [ + { + icon: 'supportForAllEthereumStandards', + title: 'Perfect for developers who need a simple drop-in marketplace', + description: ( + <ul> + <li>Quickly launch a market for your project’s token</li> + <li>Seamlessly create an in-game marketplace for digital items and collectables</li> + <li>Easily build a 0x relayer for your local market</li> + </ul> + ), + }, +]; + +export class NextLaunchKit extends React.Component { + public state = { + isContactModalOpen: false, + }; + public render(): React.ReactNode { + return ( + <SiteWrap theme="dark"> + <DocumentTitle title="0x Launch Kit: Launch a relayer in under a minute" /> + <Hero + isLargeTitle={false} + isFullWidth={false} + title="0x Launch Kit" + description="Launch a relayer in under a minute" + figure={<Icon name="launchKit" size="hero" margin={['small', 0, 'small', 0]} />} + actions={<HeroActions />} + /> + + <Section bgColor="dark" isFlex={true} maxWidth="1170px"> + <Definition + title="Networked Liquidity Pool" + titleSize="small" + description="Tap into and share liquidity with other relayers" + icon="networkedLiquidity" + iconSize="medium" + isInline={true} + /> + + <Definition + title="Extensible Code Repo" + titleSize="small" + description="Fork and extend to support modes of exchange" + icon="code-repo" + iconSize="medium" + isInline={true} + /> + + <Definition + title="Exchange Ethereum based Tokens" + titleSize="small" + description="Enable trading for any ERC-20 or ERC-721 asset" + icon="eth-based-tokens" + iconSize="medium" + isInline={true} + /> + </Section> + + <Section> + {_.map(offersData, (item, index) => ( + <Definition + key={`offers-${index}`} + icon={item.icon} + title={item.title} + description={item.description} + isInlineIcon={true} + iconSize={240} + /> + ))} + </Section> + + <Banner + heading="Need more flexibility?" + subline="Dive into our docs, or contact us if needed" + mainCta={{ + text: 'Get Started', + href: `${constants.URL_LAUNCH_KIT}/#table-of-contents`, + shouldOpenInNewTab: true, + }} + secondaryCta={{ text: 'Get in Touch', onClick: this._onOpenContactModal.bind(this) }} + /> + <ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} /> + </SiteWrap> + ); + } + + public _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + public _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; +} + +const HeroActions = () => ( + <React.Fragment> + <Button href={constants.URL_LAUNCH_KIT} isInline={true} target="_blank"> + Get Started + </Button> + + <Button href={constants.URL_LAUNCH_KIT_BLOG_POST} isTransparent={true} isInline={true} target="_blank"> + Learn More! + </Button> + </React.Fragment> +); diff --git a/packages/website/ts/pages/launch_kit/launch_kit.tsx b/packages/website/ts/pages/launch_kit/launch_kit.tsx deleted file mode 100644 index 4ea56dbd4..000000000 --- a/packages/website/ts/pages/launch_kit/launch_kit.tsx +++ /dev/null @@ -1,335 +0,0 @@ -import { colors, Link } from '@0x/react-shared'; -import * as _ from 'lodash'; -import * as React from 'react'; -import DocumentTitle from 'react-document-title'; -import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar/top_bar'; -import { Button } from 'ts/components/ui/button'; -import { Container } from 'ts/components/ui/container'; -import { Image } from 'ts/components/ui/image'; -import { Text } from 'ts/components/ui/text'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { Deco, Key, ScreenWidths } from 'ts/types'; -import { constants } from 'ts/utils/constants'; -import { Translate } from 'ts/utils/translate'; -import { utils } from 'ts/utils/utils'; - -export interface LaunchKitProps { - location: Location; - translate: Translate; - dispatcher: Dispatcher; -} - -interface LaunchKitState { - screenWidth: ScreenWidths; -} - -const THROTTLE_TIMEOUT = 100; -const lighterBackgroundColor = '#222222'; -const darkerBackgroundColor = '#1B1B1B'; -const grayText = '#999999'; - -interface Benefit { - icon: string; - description: string; -} - -export class LaunchKit extends React.Component<LaunchKitProps, LaunchKitState> { - private readonly _throttledScreenWidthUpdate: () => void; - constructor(props: LaunchKitProps) { - super(props); - this.state = { - screenWidth: utils.getScreenWidth(), - }; - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - } - public componentDidMount(): void { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillUnmount(): void { - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - } - public render(): React.ReactNode { - return ( - <div id="launchKit" className="clearfix" style={{ color: colors.grey500 }}> - <DocumentTitle title="0x Launch Kit" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - isNightVersion={true} - style={{ backgroundColor: lighterBackgroundColor, position: 'relative' }} - translate={this.props.translate} - /> - {this._renderHero()} - {this._renderSection()} - {this._renderCallToAction()} - {this._renderDisclaimer()} - <Footer - backgroundColor={darkerBackgroundColor} - translate={this.props.translate} - dispatcher={this.props.dispatcher} - /> - </div> - ); - } - private _renderHero(): React.ReactNode { - const BENEFITS_1: Benefit[] = [ - { - icon: '/images/launch_kit/shared_liquidity.svg', - description: this.props.translate.get(Key.TapIntoAndShare, Deco.Cap), - }, - { - icon: '/images/launch_kit/fork.svg', - description: this.props.translate.get(Key.ForkAndExtend, Deco.Cap), - }, - { - icon: '/images/launch_kit/enable_trading.svg', - description: this.props.translate.get(Key.EnableTrading, Deco.Cap), - }, - ]; - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const smallButtonPadding = '12px 30px 12px 30px'; - const largeButtonPadding = '14px 60px 14px 60px'; - const left = 'col lg-col-6 md-col-6 col-12 lg-pl2 md-pl2 sm-pl0 sm-px3 sm-center'; - const flexClassName = isSmallScreen - ? 'flex items-center flex-column justify-center' - : 'flex items-center justify-center'; - return ( - <div className="clearfix pt4" style={{ backgroundColor: lighterBackgroundColor }}> - <div className="mx-auto max-width-4 clearfix"> - <div className={`${flexClassName} lg-pb4 md-pb4 sm-mb4`}> - <div className={left} style={{ color: colors.white }}> - <div - className="inline-block lg-align-middle md-align-middle sm-align-top" - style={{ - paddingLeft: isSmallScreen ? 0 : 12, - lineHeight: '36px', - }} - > - <Text - className="sm-pb2" - fontFamily="Roboto" - display="inline-block" - fontColor={colors.white} - fontWeight="bold" - lineHeight="1.3em" - letterSpacing="1px" - fontSize={isSmallScreen ? '38px' : '46px'} - > - {this.props.translate.get(Key.LaunchKit, Deco.CapWords)} - </Text> - <Container paddingTop="18px"> - <Text fontColor={colors.linkSectionGrey} fontSize="18px"> - {this.props.translate.get(Key.LaunchKitPitch, Deco.Cap)} - </Text> - </Container> - <Container - paddingTop="54px" - className={`flex clearfix sm-mx-auto ${isSmallScreen ? 'justify-center' : ''}`} - > - <Container paddingRight="20px"> - <Link to={constants.URL_LAUNCH_KIT} shouldOpenInNewTab={true}> - <Button - padding={isSmallScreen ? smallButtonPadding : largeButtonPadding} - borderRadius="4px" - borderColor={colors.white} - > - <Text fontSize="16px" fontWeight="bold"> - {this.props.translate.get(Key.GetStarted, Deco.Cap)} - </Text> - </Button> - </Link> - </Container> - <div> - <Link to={constants.URL_LAUNCH_KIT_BLOG_POST} shouldOpenInNewTab={true}> - <Button - backgroundColor={lighterBackgroundColor} - borderColor={colors.white} - fontColor={colors.white} - padding={isSmallScreen ? smallButtonPadding : largeButtonPadding} - borderRadius="4px" - > - <Text fontSize="16px" fontWeight="bold" fontColor={colors.white}> - {this.props.translate.get(Key.LearnMore, Deco.Cap)} - </Text> - </Button> - </Link> - </div> - </Container> - </div> - </div> - <Container - marginTop={isSmallScreen ? '60px' : '30px'} - marginBottom="30px" - marginLeft="15px" - marginRight="15px" - > - <Image - src="/images/launch_kit/0x_cupboard.svg" - maxWidth={isSmallScreen ? '75%' : '100%'} - height="auto" - /> - </Container> - </div> - </div> - {this._renderBenefits(BENEFITS_1)} - </div> - ); - } - private _renderSection(): React.ReactNode { - const BENEFITS_2: Benefit[] = [ - { - icon: '/images/launch_kit/secondary_market.svg', - description: this.props.translate.get(Key.QuicklyLaunch, Deco.Cap), - }, - { - icon: '/images/launch_kit/in_game_marketplace.svg', - description: this.props.translate.get(Key.SeemlesslyCreate, Deco.Cap), - }, - { - icon: '/images/launch_kit/local_market.svg', - description: this.props.translate.get(Key.LocalMarket, Deco.Cap), - }, - ]; - return ( - <div className="clearfix pb4" style={{ backgroundColor: darkerBackgroundColor }}> - <Container - className="mx-auto" - textAlign="center" - paddingTop="89px" - paddingBottom="89px" - maxWidth="421px" - paddingLeft="10px" - paddingRight="10px" - > - <Text fontSize="26px" lineHeight="37px" fontWeight="medium" fontColor={colors.white}> - {this.props.translate.get(Key.PerfectForDevelopers, Deco.Cap)} - </Text> - </Container> - {this._renderBenefits(BENEFITS_2)} - </div> - ); - } - private _renderCallToAction(): React.ReactNode { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const smallButtonPadding = '8px 14px 8px 14px'; - const largeButtonPadding = '8px 14px 8px 14px'; - return ( - <Container - className="clearfix" - backgroundColor={lighterBackgroundColor} - paddingTop="90px" - paddingBottom="90px" - > - <Container className="clearfix mx-auto" maxWidth="850px"> - <Container className="lg-left md-left sm-mx-auto sm-pb3" width="348px"> - <Text fontColor={colors.white} fontSize="18px"> - View our comprehensive documentation to start building today. - </Text> - </Container> - <Container - className={`lg-right md-right flex clearfix sm-mx-auto ${ - isSmallScreen ? 'justify-center' : '' - }`} - paddingTop="5px" - > - <Container paddingRight="20px"> - <Link to={`${constants.URL_LAUNCH_KIT}/#table-of-contents`} shouldOpenInNewTab={true}> - <Button - padding={isSmallScreen ? smallButtonPadding : largeButtonPadding} - borderRadius="4px" - backgroundColor={lighterBackgroundColor} - borderColor={colors.white} - > - <Text fontSize="16px" fontWeight="bold" fontColor={colors.white}> - {this.props.translate.get(Key.ExploreTheDocs, Deco.Cap)} - </Text> - </Button> - </Link> - </Container> - <div> - <Link to={constants.URL_ZEROEX_CHAT} shouldOpenInNewTab={true}> - <Button - padding={isSmallScreen ? smallButtonPadding : largeButtonPadding} - borderRadius="4px" - > - <Text fontSize="16px" fontWeight="bold"> - {this.props.translate.get(Key.GetInTouch, Deco.Cap)} - </Text> - </Button> - </Link> - </div> - </Container> - </Container> - </Container> - ); - } - private _renderBenefits(benefits: Benefit[]): React.ReactNode { - return ( - <Container className="lg-flex md-flex justify-between mx-auto pb4" maxWidth="890px"> - {_.map(benefits, benefit => { - return ( - <Container className="mx-auto sm-pb4" width="240px"> - <Container textAlign="center"> - <img src={benefit.icon} /> - </Container> - <Container paddingTop="26px"> - <Text - fontSize="18px" - lineHeight="28px" - textAlign="center" - fontColor={colors.linkSectionGrey} - > - {benefit.description} - </Text> - </Container> - </Container> - ); - })} - </Container> - ); - } - private _renderDisclaimer(): React.ReactNode { - return ( - <Container - className="clearfix" - backgroundColor={darkerBackgroundColor} - paddingTop="70px" - paddingBottom="70px" - > - <Container className="mx-auto" maxWidth="850px" paddingLeft="20px" paddingRight="20px"> - <Text fontColor={grayText} fontSize="10px"> - <b>Disclaimer:</b> The laws and regulations applicable to the use and exchange of digital assets - and blockchain-native tokens, including through any software developed using the licensed work - created by ZeroEx Intl. (the “Work”), vary by jurisdiction. As set forth in the Apache License, - Version 2.0 applicable to the Work, developers are “solely responsible for determining the - appropriateness of using or redistributing the Work,” which includes responsibility for ensuring - compliance with any such applicable laws and regulations. - </Text> - <Container paddingTop="15px"> - <Text fontColor={grayText} fontSize="10px"> - See the{' '} - <Link - to={constants.URL_APACHE_LICENSE} - shouldOpenInNewTab={true} - textDecoration="underline" - > - Apache License, Version 2.0 - </Link>{' '} - for the specific language governing all applicable permissions and limitations. - </Text> - </Container> - </Container> - </Container> - ); - } - private _updateScreenWidth(): void { - const newScreenWidth = utils.getScreenWidth(); - if (newScreenWidth !== this.state.screenWidth) { - this.setState({ - screenWidth: newScreenWidth, - }); - } - } -} diff --git a/packages/website/ts/pages/market_maker.tsx b/packages/website/ts/pages/market_maker.tsx new file mode 100644 index 000000000..854870358 --- /dev/null +++ b/packages/website/ts/pages/market_maker.tsx @@ -0,0 +1,162 @@ +import * as _ from 'lodash'; +import { opacify } from 'polished'; +import * as React from 'react'; + +import { Banner } from 'ts/components/banner'; +import { Button } from 'ts/components/button'; +import { Action, Definition } from 'ts/components/definition'; +import { Hero } from 'ts/components/hero'; +import { ModalContact, ModalContactType } from 'ts/components/modals/modal_contact'; +import { Section } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; +import { colors } from 'ts/style/colors'; +import { WebsitePaths } from 'ts/types'; + +interface OfferData { + icon: string; + title: string; + description: string; + links?: Action[]; +} +export interface NextMarketMakerProps {} + +export class NextMarketMaker extends React.Component<NextMarketMakerProps> { + public state = { + isContactModalOpen: false, + }; + + private readonly _offersData: OfferData[]; + + constructor(props: NextMarketMakerProps) { + super(props); + this._offersData = [ + { + icon: 'supportForAllEthereumStandards', + title: 'Comprehensive Tutorials', + description: + 'Stay on the bleeding edge of crypto by learning how to market make on decentralized exchanges. The network of 0x relayers provides market makers a first-mover advantage to capture larger spreads, find arbitrage opportunities, and trade on new types of exchanges like prediction markets and non-fungible token marketplaces.', + links: [ + { + label: 'Explore the Docs', + url: `${WebsitePaths.Wiki}#Market-Making-on-0x`, + }, + ], + }, + { + icon: 'generateRevenueForYourBusiness-large', + title: 'Market Making Compensation', + description: 'Accepted applicants can receive up to $15,000 for completing onboarding', + }, + { + icon: 'getInTouch', + title: 'Dedicated Support', + description: + 'The 0x team will provide 1:1 onboarding assistance and promptly answer all your questions. They will walk you through the tutorials so that you know how to read 0x order types, spin up an Ethereum node, and execute trades on the blockchain.', + links: [ + { + label: 'Contact Us', + onClick: this._onOpenContactModal, + shouldUseAnchorTag: true, + }, + ], + }, + ]; + } + + public render(): React.ReactNode { + return ( + <SiteWrap theme="light"> + <Hero + maxWidth="865px" + maxWidthHeading="715px" + isLargeTitle={false} + isFullWidth={false} + isCenteredMobile={false} + title="Bring liquidity to the markets of the future" + description="Market makers (MMs) are important stakeholders in the 0x ecosystem. The Market Making Program provides a set of resources that help onboard MMs to bring liquidity to the 0x network. The Program includes tutorials, monetary incentives, and 1:1 support from the 0x team." + actions={this._renderHeroActions()} + /> + + <Section bgColor="light" isFlex={true} maxWidth="1170px"> + <Definition + title="Secure Trading" + titleSize="small" + description="Take full custody of your assets to eliminate counterparty risk" + icon="secureTrading" + iconSize="medium" + isInline={true} + /> + + <Definition + title="Networked Liquidity Pool" + titleSize="small" + description="Use one pool of capital across multiple relayers to trade against a large group of takers" + icon="networkedLiquidity" + iconSize="medium" + isInline={true} + /> + + <Definition + title="Low Cost" + titleSize="small" + description="Pay no gas fees to make 0x orders" + icon="low-cost" + iconSize="medium" + isInline={true} + /> + </Section> + + <Section> + {_.map(this._offersData, (item, index) => ( + <Definition + key={`offers-${index}`} + icon={item.icon} + title={item.title} + description={item.description} + isInlineIcon={true} + iconSize={240} + fontSize="medium" + actions={item.links} + /> + ))} + </Section> + + <Banner + heading="Start trading today." + subline="Check out our Market Making tutorials to get started" + mainCta={{ text: 'Tutorials', href: `${WebsitePaths.Wiki}#Market-Making-on-0x` }} + secondaryCta={{ text: 'Apply Now', onClick: this._onOpenContactModal }} + /> + <ModalContact + isOpen={this.state.isContactModalOpen} + onDismiss={this._onDismissContactModal} + modalContactType={ModalContactType.MarketMaker} + /> + </SiteWrap> + ); + } + + private readonly _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + private readonly _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; + + private readonly _renderHeroActions = () => ( + <> + <Button href={`${WebsitePaths.Wiki}#Market-Making-on-0x`} bgColor="dark" isInline={true}> + Get Started + </Button> + <Button + onClick={this._onOpenContactModal} + borderColor={opacify(0.4)(colors.brandDark)} + isTransparent={true} + isInline={true} + > + Apply Now + </Button> + </> + ); +} diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index a7992a8fa..6abd8fc80 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Footer } from 'ts/components/footer'; +import { Footer } from 'ts/components/old_footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { FullscreenMessage } from 'ts/pages/fullscreen_message'; import { Dispatcher } from 'ts/redux/dispatcher'; diff --git a/packages/website/ts/pages/why.tsx b/packages/website/ts/pages/why.tsx new file mode 100644 index 000000000..197ce5fc9 --- /dev/null +++ b/packages/website/ts/pages/why.tsx @@ -0,0 +1,309 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import DocumentTitle from 'react-document-title'; +import ScrollableAnchor, { configureAnchors } from 'react-scrollable-anchor'; +import styled from 'styled-components'; + +import { Banner } from 'ts/components/banner'; +import { Button } from 'ts/components/button'; +import { Definition } from 'ts/components/definition'; +import { Hero } from 'ts/components/hero'; +import { Column, Section, WrapSticky } from 'ts/components/newLayout'; +import { SiteWrap } from 'ts/components/siteWrap'; +import { Slide, Slider } from 'ts/components/slider/slider'; +import { Heading } from 'ts/components/text'; + +import { ModalContact } from '../components/modals/modal_contact'; + +const offersData = [ + { + icon: 'robustSmartContracts', + title: 'Robust Smart Contracts', + description: `0x Protocol's smart contracts have been put through two rounds of rigorous security audits.`, + }, + { + icon: 'extensibleArchitecture', + title: 'Extensible Architecture', + description: `0x's modular pipeline enables you to plug in your own smart contracts through an extensible API.`, + }, + { + icon: 'eficientDesign', + title: 'Efficient Design', + description: `0x’s off-chain order relay with on-chain settlement is a gas efficient approach to p2p exchange, reducing blockchain bloat.`, + }, +]; + +const functionalityData = [ + { + icon: 'secureTrading', + title: 'Secure Non-custodial Trading', + description: 'Enable tokens to be traded wallet-to-wallet with no deposits or withdrawals.', + }, + { + icon: 'flexibleOrders', + title: 'Flexible Order Types', + description: 'Choose to sell assets at a specific “buy it now” price or allow potential buyers to submit bids.', + }, + { + icon: 'buildBusiness', + title: 'Build a Business', + description: + 'Monetize your product by taking fees on each transaction and join a growing number of relayers in the 0x ecosystem.', + }, +]; + +const useCaseSlides = [ + { + icon: 'gamingAndCollectibles', + title: 'Games & Collectibles', + description: + 'Artists and game makers are tokenizing digital art and in-game items known as non-fungible tokens (NFTs). 0x enables these creators to add exchange functionality by providing the ability to build marketplaces for NFT trading.', + }, + { + icon: 'predictionMarkets', + title: 'Prediction Markets', + description: + 'Decentralized prediction markets and cryptodervivative platforms generate sets of tokens that represent a financial stake in the outcomes of events. 0x allows these tokens to be instantly tradable in liquid markets.', + }, + { + icon: 'orderBooks', + title: 'Order Books', + description: + 'There are thousands of decentralized apps and protocols that have native utility tokens. 0x provides professional exchanges with the ability to host order books and facilitates the exchange of these assets.', + }, + { + icon: 'decentralisedLoans', + title: 'Decentralized Loans', + description: + 'Efficient lending requires liquid markets where investors can buy and re-sell loans. 0x enables an ecosystem of lenders to self-organize and efficiently determine market prices for all outstanding loans.', + }, + { + icon: 'stableTokens', + title: 'Stable Tokens', + description: + 'Novel economic constructs such as stable coins require efficient, liquid markets to succeed. 0x will facilitate the underlying economic mechanisms that allow these tokens to remain stable.', + }, +]; + +configureAnchors({ offset: -60 }); + +export class NextWhy extends React.Component { + public state = { + isContactModalOpen: false, + }; + public render(): React.ReactNode { + const buildAction = ( + <Button href="/docs" isWithArrow={true} isAccentColor={true}> + Build on 0x + </Button> + ); + return ( + <SiteWrap theme="dark"> + <DocumentTitle title="Features & Benefits - 0x" /> + <Hero + title="The exchange layer for the crypto economy" + description="The world's assets are becoming tokenized on public blockchains. 0x Protocol is free, open-source infrastracture that developers and businesses utilize to build products that enable the purchasing and trading of crypto tokens." + actions={buildAction} + /> + + <Section bgColor="dark" isFlex={true} maxWidth="1170px"> + <Definition + title="Support for all Ethereum Standards" + titleSize="small" + description="0x Protocol facitilites the decentralized exchange of a growing number of Ethereum-based tokens, including all ERC-20 and ERC-721 assets." + icon="supportForAllEthereumStandards" + iconSize="large" + isInline={true} + /> + + <Definition + title="Networked Liquidity" + titleSize="small" + description="0x is lowering the barrier to entry by building a layer of networked liquidity that allows businesses to tap into a shared pool of digital assets." + icon="networkedLiquidity" + iconSize="large" + isInline={true} + /> + + <Definition + title="Flexible Integration" + titleSize="small" + description="0x is a modular system that enables businesses and projects, known as relayers, to easily add exchange functionality to any product experience." + icon="flexibleIntegration" + iconSize="large" + isInline={true} + /> + </Section> + + <Section maxWidth="1170px" isFlex={true} isFullWidth={true}> + <Column> + <NavStickyWrap offsetTop="130px"> + <ChapterLink href="#benefits">Benefits</ChapterLink> + <ChapterLink href="#cases">Use Cases</ChapterLink> + <ChapterLink href="#functionality">Features</ChapterLink> + </NavStickyWrap> + </Column> + + <Column width="55%" maxWidth="826px"> + <Column width="100%" maxWidth="560px" padding="0 30px 0 0"> + <ScrollableAnchor id="benefits"> + <SectionWrap> + <SectionTitle size="medium" marginBottom="60px" isNoBorder={true}> + What 0x offers + </SectionTitle> + + {_.map(offersData, (item, index) => ( + <Definition + key={`offers-${index}`} + icon={item.icon} + title={item.title} + titleSize="small" + description={item.description} + isWithMargin={true} + /> + ))} + </SectionWrap> + </ScrollableAnchor> + + <ScrollableAnchor id="cases"> + <SectionWrap isNotRelative={true}> + <SectionTitle size="medium" marginBottom="60px"> + Use Cases + </SectionTitle> + <Slider> + {_.map(useCaseSlides, (item, index) => ( + <Slide + key={`useCaseSlide-${index}`} + heading={item.title} + text={item.description} + icon={item.icon} + /> + ))} + </Slider> + </SectionWrap> + </ScrollableAnchor> + + <ScrollableAnchor id="functionality"> + <SectionWrap> + <SectionTitle size="medium" marginBottom="60px"> + Exchange Functionality + </SectionTitle> + + {_.map(functionalityData, (item, index) => ( + <Definition + key={`functionality-${index}`} + icon={item.icon} + title={item.title} + titleSize="small" + description={item.description} + isWithMargin={true} + /> + ))} + </SectionWrap> + </ScrollableAnchor> + </Column> + </Column> + </Section> + + <Banner + heading="Ready to get started?" + subline="Dive into our docs, or contact us if needed" + mainCta={{ text: 'Get Started', href: '/docs' }} + secondaryCta={{ text: 'Get in Touch', onClick: this._onOpenContactModal.bind(this) }} + /> + <ModalContact isOpen={this.state.isContactModalOpen} onDismiss={this._onDismissContactModal} /> + </SiteWrap> + ); + } + + public _onOpenContactModal = (): void => { + this.setState({ isContactModalOpen: true }); + }; + + public _onDismissContactModal = (): void => { + this.setState({ isContactModalOpen: false }); + }; +} + +interface SectionProps { + isNotRelative?: boolean; +} + +const SectionWrap = + styled.div < + SectionProps > + ` + position: ${props => !props.isNotRelative && 'relative'}; + + & + & { + padding-top: 60px; + margin-top: 60px; + } + + @media (min-width: 768px) { + & + &:before { + width: 100vw; + } + } + + @media (max-width: 768px) { + text-align: left; + + & + &:before { + width: 100%; + } + } +`; + +interface SectionTitleProps { + isNoBorder?: boolean; +} +const SectionTitle = + styled(Heading) < + SectionTitleProps > + ` + position: relative; + + ${props => + !props.isNoBorder && + ` + &:before { + content: ''; + width: 100vw; + position: absolute; + top: -53px; + left: 0; + height: 1px; + background-color: #3d3d3d; + } + `} + + + @media (max-width: 768px) { + &:before { + width: calc(100vw - 60px); + } + } +`; + +const NavStickyWrap = styled(WrapSticky)` + padding-left: 60px; + z-index: 15; + + @media (max-width: 768px) { + display: none; + } +`; + +const ChapterLink = styled.a` + color: ${props => props.theme.textColor}; + font-size: 22px; + margin-bottom: 25px; + display: block; + opacity: 0.8; + + &:hover, + &:active { + opacity: 1; + } +`; |