this.mapContainer = el} className="absolute top right left bottom" style={{ height: "100%" }} />
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/components/Masthead.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Row, Col, Avatar, Tag } from 'antd';
4 | import styled from 'styled-components';
5 |
6 | const Page1Container = styled.div`
7 | padding-top: 30px;
8 | padding-bottom: 30px;
9 | background-color: #f8f9fa;
10 | `
11 |
12 | const Outerbox = styled.div`
13 | width: 100%;
14 | height: 100%;
15 | display: block;
16 | position: relative;
17 | opacity: 1;
18 | `
19 |
20 | const OuterboxImg = styled.img`
21 | width: 100%;
22 | height: 100%;
23 | `
24 |
25 | const InnerBox = styled.div`
26 | height: auto;
27 | width: 64%; /* 84% */
28 | opacity: 1;
29 | top: 5%; /* 4% */
30 | left: 17%; /* 8% */
31 | position: absolute;
32 | padding: 0;
33 | `
34 |
35 | const InnerBoxImg = styled.img`
36 | height: 100%;
37 | width: 100%;
38 | `
39 |
40 |
41 | export default function CompanyHead({ isMobile, isLoading, data }) {
42 |
43 | const Screenshot = props => (
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 |
52 |
53 | const name = isLoading ? 'Loading Company...' : data.name
54 | const avatarURL = isLoading ? '' : data.logo
55 | const avatar = avatarURL == '' ? name : null
56 | const description = isLoading ? 'Loading company description...' : data.description
57 | const landingURL = isLoading ? 'https://via.placeholder.com/900x563' : (data.landingpage == '' ? 'https://fillmurray.com/900/563' : data.landingpage)
58 |
59 | const tag = isLoading ? 'Unknown' : data.status == '' ? 'Unknown' : data.status
60 | const colorForStatus = status => {
61 | if (!status) return "gray"
62 | if (status.match(/live/i)) return "#ffc108" // yellow
63 | if (status.match(/dead/i)) return "#dc3545" // red
64 | if (status.match(/exit/i)) return "#28a745" // green
65 | return "gray"
66 | }
67 |
68 |
69 | return (
70 |
71 |
72 |
73 | {avatar}
74 |
75 |
76 | {name}
77 |
78 | {description} {tag.toUpperCase()}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | CompanyHead.propTypes = {
91 | isMobile: PropTypes.bool,
92 | isLoading: PropTypes.bool,
93 | data: PropTypes.object,
94 | };
--------------------------------------------------------------------------------
/src/app/components/Page1.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Router from "next/router";
4 | import { Card, Row, Col, Avatar, Icon } from 'antd';
5 | import styled from 'styled-components';
6 |
7 | const Page1Container = styled.div`
8 | padding-top: 30px;
9 | padding-bottom: 30px;
10 | background-color: #f8f9fa;
11 | .ant-card-head {
12 | background-color: #d0021b11;
13 | }
14 | i {
15 | margin-left: 80px;
16 | }
17 | `
18 | const PageHeader = styled.h1`
19 | text-align: center;
20 | padding-bottom: 30px;
21 | `
22 |
23 |
24 | export default function Page1({ isMobile }) {
25 |
26 | const { Meta } = Card;
27 |
28 | const onCardSelect = (e,props) => {
29 | const target = props.link || "exit"
30 | console.log("Select Card: ", target)
31 | Router.push(`/ranking?id=${target}`,`/ranking/${target}`)
32 | }
33 |
34 | const RankingBox = props => (
35 |
}
38 | onClick={(e) => { onCardSelect(e,props) } }
39 | >
40 | {props.info}
41 |
42 | )
43 |
44 | return (
45 |
46 | Top 100 Ranking
47 |
48 |
49 | Funding
50 | Exit
51 | Employees
52 | Alexa
53 |
54 |
55 |
56 | Funding
57 | Exit
58 | Employees
59 | Alexa
60 |
61 |
62 | );
63 | }
64 | Page1.propTypes = {
65 | isMobile: PropTypes.bool,
66 | };
--------------------------------------------------------------------------------
/src/app/components/Page2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Router from "next/router";
4 | import { Card, Row, Col, Table, Tag } from 'antd';
5 | import FirebaseProvider from '../lib/FirebaseProvider';
6 | import styled from 'styled-components';
7 | import dynamic from 'next/dynamic'
8 | const Map = dynamic(() => import('./Map.js'), {
9 | ssr: false
10 | });
11 |
12 | const Page2Container = styled.div`
13 | margin-top: 30px;
14 | margin-bottom: 30px;
15 |
16 | .batch-data-table {
17 | width: 90%;
18 | background: #fff;
19 | margin: 10px;
20 | border-color: #fff;
21 | -webkit-box-shadow: 10px 10px 28px 1px rgba(0,0,0,0.35);
22 | -moz-box-shadow: 10px 10px 28px 1px rgba(0,0,0,0.35);
23 | box-shadow: 0 2px 35px 0 rgba(0,0,0,0.04), 0 2px 4px 0 rgba(0,0,0,0.02), 0 27px 24px 0 rgba(0,0,0,0.04);
24 | background: #FFFFFF;
25 |
26 | tr:hover {
27 | background-color: #f5f5f5;
28 | }
29 |
30 | .ant-tag {
31 | margin-right: 1px;
32 | }
33 |
34 | }
35 |
36 | .batch-data-table-row {
37 | height: 18px;
38 | background: #fff;
39 | padding: 0px;
40 | border-color: #fff;
41 | border-bottom-color: #f00;
42 |
43 | th, td {
44 | padding-top: 2px;
45 | padding-left: 15px;
46 | text-align: left;
47 | }
48 |
49 | }
50 |
51 | .map-box {
52 | height: 600px;
53 | }
54 |
55 | .map-box .mapboxgl-map {
56 | -webkit-box-shadow: 10px 10px 28px 1px rgba(0,0,0,0.35);
57 | -moz-box-shadow: 10px 10px 28px 1px rgba(0,0,0,0.35);
58 | box-shadow: 10px 10px 28px 1px rgba(0,0,0,0.35);
59 | }
60 | `
61 |
62 | const PageHeader = styled.h1`
63 | text-align: center;
64 | `
65 |
66 | export default function Page2({ isMobile }) {
67 |
68 | const MapBox = props =>
{props.title} {props.children};
69 |
70 |
71 | const onRowSelect = record => {
72 | // console.log("Select record ", record)
73 | // redirect('/cohort/testing','/cohort?id=testing')
74 | Router.push(`/cohort?id=${record.batch}`,`/cohort/${record.batch}`)
75 | }
76 |
77 |
78 | const tableCount = data => {
79 | const tableItems = data.map((record) =>
80 |
{ onRowSelect(record) } }>
81 |
82 | {record.batch}
83 |
84 |
85 | {record.count}
86 |
87 |
88 | );
89 | return (
90 |
91 | )
92 | }
93 |
94 | const tableStatus = data => {
95 | const tableItems = data.map((record) =>
96 |
{ onRowSelect(record) } }>
97 |
98 | {record.batch}
99 |
100 |
101 | {record.exited}
102 | {record.live}
103 | {record.dead}
104 |
105 |
106 | );
107 | return (
108 |
109 | )
110 | }
111 |
112 |
113 | const tableFunding = data => {
114 | const tableItems = data.map((record) =>
115 |
{ onRowSelect(record) } }>
116 |
117 | {record.batch}
118 |
119 |
120 | {record.mega}
121 | {record.mini}
122 | {record.seed}
123 | {record.none}
124 |
125 |
126 | );
127 | return (
128 |
129 | )
130 | }
131 |
132 | // Generated from /scripts/generateAnalytics
133 |
134 | const batchData = [{"batch":"S06","count":3,"percent":2.158273381294964,"exited":2,"live":1,"dead":0,"mega":3,"mini":0,"seed":0,"none":0},{"batch":"W06","count":5,"percent":3.597122302158273,"exited":5,"live":0,"dead":0,"mega":1,"mini":0,"seed":1,"none":3},{"batch":"S07","count":9,"percent":6.474820143884892,"exited":7,"live":2,"dead":0,"mega":4,"mini":0,"seed":3,"none":2},{"batch":"W07","count":8,"percent":5.755395683453238,"exited":6,"live":2,"dead":0,"mega":2,"mini":0,"seed":4,"none":2},{"batch":"W08","count":8,"percent":5.755395683453238,"exited":4,"live":4,"dead":0,"mega":2,"mini":0,"seed":2,"none":4},{"batch":"S08","count":6,"percent":4.316546762589928,"exited":5,"live":1,"dead":0,"mega":2,"mini":0,"seed":2,"none":2},{"batch":"W09","count":9,"percent":6.474820143884892,"exited":5,"live":4,"dead":0,"mega":1,"mini":1,"seed":2,"none":5},{"batch":"S09","count":13,"percent":9.352517985611511,"exited":7,"live":6,"dead":0,"mega":5,"mini":0,"seed":2,"none":6},{"batch":"W10","count":16,"percent":11.510791366906476,"exited":13,"live":3,"dead":0,"mega":2,"mini":2,"seed":8,"none":4},{"batch":"S10","count":25,"percent":17.985611510791365,"exited":16,"live":9,"dead":0,"mega":9,"mini":1,"seed":7,"none":8},{"batch":"W11","count":30,"percent":21.58273381294964,"exited":10,"live":20,"dead":0,"mega":11,"mini":0,"seed":10,"none":9},{"batch":"S11","count":43,"percent":30.935251798561154,"exited":19,"live":24,"dead":0,"mega":17,"mini":2,"seed":14,"none":10},{"batch":"S12","count":52,"percent":37.410071942446045,"exited":17,"live":35,"dead":0,"mega":14,"mini":3,"seed":17,"none":18},{"batch":"W12","count":46,"percent":33.093525179856115,"exited":21,"live":25,"dead":0,"mega":17,"mini":4,"seed":13,"none":12},{"batch":"W13","count":35,"percent":25.179856115107913,"exited":8,"live":27,"dead":0,"mega":11,"mini":5,"seed":15,"none":4},{"batch":"S13","count":32,"percent":23.021582733812952,"exited":12,"live":20,"dead":0,"mega":12,"mini":3,"seed":15,"none":2},{"batch":"S14","count":61,"percent":43.884892086330936,"exited":8,"live":53,"dead":0,"mega":14,"mini":3,"seed":37,"none":7},{"batch":"W14","count":63,"percent":45.32374100719424,"exited":13,"live":50,"dead":0,"mega":17,"mini":5,"seed":34,"none":7},{"batch":"W15","count":97,"percent":69.7841726618705,"exited":19,"live":78,"dead":0,"mega":27,"mini":7,"seed":52,"none":11},{"batch":"S15","count":90,"percent":64.74820143884892,"exited":8,"live":82,"dead":0,"mega":22,"mini":6,"seed":53,"none":9},{"batch":"W16","count":114,"percent":82.01438848920863,"exited":3,"live":111,"dead":0,"mega":19,"mini":15,"seed":70,"none":10},{"batch":"S16","count":97,"percent":69.7841726618705,"exited":4,"live":93,"dead":0,"mega":16,"mini":7,"seed":60,"none":14},{"batch":"S17","count":112,"percent":80.57553956834532,"exited":5,"live":107,"dead":0,"mega":7,"mini":3,"seed":88,"none":14},{"batch":"W17","count":110,"percent":79.13669064748201,"exited":3,"live":107,"dead":0,"mega":7,"mini":12,"seed":67,"none":24},{"batch":"S18","count":91,"percent":65.46762589928058,"exited":0,"live":91,"dead":0,"mega":2,"mini":3,"seed":12,"none":74},{"batch":"W18","count":139,"percent":100,"exited":0,"live":139,"dead":0,"mega":1,"mini":5,"seed":103,"none":30}]
135 |
136 |
137 |
138 | return (
139 |
140 | Cohort Analysis
141 |
142 |
143 |
144 | Companies by Cohort
145 | Number of companies founded by cohort year.
146 | {tableCount(batchData)}
147 |
148 |
149 | Status Outcome by Cohort
150 | Outcome grouped by Exited , Live and Dead .
151 | {tableStatus(batchData)}
152 |
153 |
154 | {/*
155 |
156 | */}
157 | Funding by Cohort
158 | Total funding grouped by >10MM , 5~10MM and 0~5MM .
159 | {tableFunding(batchData)}
160 |
161 |
162 |
163 |
164 | );
165 | }
166 | Page2.propTypes = {
167 | isMobile: PropTypes.bool,
168 | };
--------------------------------------------------------------------------------
/src/app/components/Page3.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Link from 'next/link'
4 | import { Tabs, Row, Col, Table, Tag, Icon } from 'antd';
5 | import FirebaseProvider from '../lib/FirebaseProvider';
6 | import styled from 'styled-components';
7 |
8 | const Page3Container = styled.div`
9 | margin-top: 30px;
10 | padding-bottom: 20px;
11 | background-color: #f8f9fa;
12 |
13 | .ant-tabs-left > .ant-tabs-bar .ant-tabs-tab {
14 | text-align: left;
15 | height: 18px;
16 | }
17 | `
18 | const PageHeader = styled.h1`
19 | text-align: center;
20 | padding-top: 30px;
21 | padding-bottom: 30px;
22 | `
23 |
24 |
25 | export default function Page3({ isMobile, batch }) {
26 |
27 | const onRowSelect = record => {
28 | console.log("Select record ", record)
29 | }
30 |
31 | const colorForStatus = status => {
32 | if (status.match(/live/i)) return "#ffc108" // yellow
33 | if (status.match(/dead/i)) return "#dc3545" // red
34 | if (status.match(/exit/i)) return "#28a745" // green
35 | return "gray"
36 | }
37 |
38 | const columns = [{
39 | title: 'Name',
40 | dataIndex: 'name',
41 | key: 'name',
42 | render: ((text,record) =>
{text} ),
43 | }, {
44 | title: 'Batch',
45 | dataIndex: 'batch',
46 | key: 'batch'
47 | }, {
48 | title: 'Description',
49 | dataIndex: 'description',
50 | key: 'description'
51 | }, {
52 | title: 'Category',
53 | dataIndex: 'category',
54 | key: 'category'
55 | }, {
56 | title: 'Status',
57 | dataIndex: 'status',
58 | key: 'status',
59 | render: ((tag) =>
{tag.toUpperCase()} )
60 | }];
61 |
62 | const batchFilter = batch || 'W18'
63 |
64 | var filter = ['batch', '==', batchFilter]
65 |
66 | const TabPane = Tabs.TabPane
67 |
68 | const QueryFilterTable = category => {
69 | var categoryFilter = filter
70 | if (category != "All") {
71 | categoryFilter = [filter,['category', '==', category]]
72 | }
73 | return (
74 |
75 |
76 | { ({error, isLoading, data}) => {
77 |
78 | if (error) { console.error("Error loading users ", error)}
79 |
80 | return(
81 | record.id}
85 | onRow={(record) => ({
86 | onClick: () => { onRowSelect(record); }
87 | })}
88 | loading={isLoading}
89 | pagination={true} />
90 | )
91 | }}
92 |
93 |
94 | )
95 | }
96 |
97 | return (
98 |
99 | Recent Investment Cohort {batchFilter}
100 |
101 |
102 |
103 |
110 | All} key="1">
111 | {QueryFilterTable("All")}
112 |
113 |
114 | Aerospace} key="2">
115 | {QueryFilterTable("Aerospace")}
116 |
117 |
118 | Agriculture} key="3">
119 | {QueryFilterTable("Agriculture")}
120 |
121 |
122 | AI and ML} key="4">
123 | {QueryFilterTable("AI and ML")}
124 |
125 |
126 | Blockchain} key="5">
127 | {QueryFilterTable("Blockchain")}
128 |
129 |
130 | Consumer} key="6">
131 | {QueryFilterTable("Consumer")}
132 |
133 |
134 | Dev Tools} key="7">
135 | {QueryFilterTable("Dev Tools")}
136 |
137 |
138 | Education} key="8">
139 | {QueryFilterTable("Education")}
140 |
141 |
142 | Entertainment} key="9">
143 | {QueryFilterTable("Entertainment")}
144 |
145 |
146 | Fintech} key="10">
147 | {QueryFilterTable("Fintech")}
148 |
149 |
150 | Government} key="11">
151 | {QueryFilterTable("Government")}
152 |
153 |
154 | Healthcare} key="12">
155 | {QueryFilterTable("Healthcare")}
156 |
157 |
158 | Industrial} key="13">
159 | {QueryFilterTable("Industrial")}
160 |
161 |
162 | Real Estate} key="14">
163 | {QueryFilterTable("Real Estate")}
164 |
165 |
166 | Resources} key="15">
167 | {QueryFilterTable("Resources")}
168 |
169 |
170 | Transport} key="16">
171 | {QueryFilterTable("Transport")}
172 |
173 |
174 | Other SaaS} key="17">
175 | {QueryFilterTable("Other SaaS")}
176 |
177 |
178 | Nonprofit} key="18">
179 | {QueryFilterTable("Nonprofit")}
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 | );
188 | }
189 | Page3.propTypes = {
190 | isMobile: PropTypes.bool,
191 | };
--------------------------------------------------------------------------------
/src/app/components/RankingTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Link from 'next/link'
4 | import { Tabs, Row, Col, Table, Tag, Icon, Select } from 'antd';
5 | import FirebaseProvider from '../lib/FirebaseProvider';
6 | import styled from 'styled-components';
7 |
8 | const RankingTableContainer = styled.div`
9 | margin-top: 30px;
10 | margin-bottom: 30px;
11 | background-color: #f8f9fa;
12 | `
13 | const PageHeader = styled.h1`
14 | text-align: center;
15 | padding-top: 30px;
16 | padding-bottom: 30px;
17 | `
18 |
19 | const Option = Select.Option;
20 | const TabPane = Tabs.TabPane
21 |
22 |
23 |
24 | class RankingTable extends React.PureComponent {
25 | static propTypes = {
26 | sort: PropTypes.string,
27 | isMoblie: PropTypes.bool,
28 | }
29 | state = {
30 | sort: this.props.sort || 'funding',
31 | isMobile: this.props.isMobile || false
32 | };
33 |
34 |
35 | handleMenuChange = value => {
36 | // console.log("Select option ", value)
37 | this.setState({
38 | sort: value.key
39 | })
40 | }
41 |
42 | onRowSelect = record => {
43 | console.log("Select record ", record)
44 | }
45 |
46 | render() {
47 |
48 | const colorForStatus = status => {
49 | if (status.match(/live/i)) return "#ffc108" // yellow
50 | if (status.match(/dead/i)) return "#dc3545" // red
51 | if (status.match(/exit/i)) return "#28a745" // green
52 | return "gray"
53 | }
54 |
55 | const formatter = new Intl.NumberFormat('en-US', {
56 | style: 'currency',
57 | currency: 'USD',
58 | minimumFractionDigits: 1
59 | });
60 |
61 | // formatter.format(2500); /* $2,500.0 */
62 |
63 | const buildColumns = () => {
64 | const name_col = {
65 | title: 'Name',
66 | dataIndex: 'name',
67 | key: 'name',
68 | render: ((text,record) => {text} ),
69 | }
70 |
71 | const funding_col = {
72 | title: 'Funding',
73 | dataIndex: 'funding',
74 | key: 'funding',
75 | render: ((millions) => formatter.format(millions)+"MM"),
76 | align: 'right'
77 | }
78 |
79 | const exit_col = {
80 | title: 'Exit',
81 | dataIndex: 'exit',
82 | key: 'exit',
83 | render: ((millions) => formatter.format(millions)+"MM"),
84 | align: 'right'
85 | }
86 |
87 | const employee_col = {
88 | title: 'Employees',
89 | dataIndex: 'employees',
90 | key: 'employees',
91 | align: 'right'
92 | }
93 | const alexa_col = {
94 | title: 'Alexa',
95 | dataIndex: 'alexa',
96 | key: 'alexa',
97 | align: 'right'
98 | }
99 |
100 | const twitter_col = {
101 | title: 'Twitter Followers',
102 | dataIndex: 'twitter',
103 | key: 'twitter',
104 | align: 'right'
105 | }
106 |
107 | const tweets_col = {
108 | title: 'Total Tweets',
109 | dataIndex: 'tweets',
110 | key: 'tweets',
111 | align: 'right'
112 | }
113 |
114 | const facebook_col = {
115 | title: 'Facebook Posts',
116 | dataIndex: 'facebook',
117 | key: 'facebook',
118 | align: 'right'
119 | }
120 |
121 | const batch_col = {
122 | title: 'Batch',
123 | dataIndex: 'batch',
124 | key: 'batch'
125 | }
126 | const desc_col = {
127 | title: 'Description',
128 | dataIndex: 'description',
129 | key: 'description'
130 | }
131 |
132 | const cat_col = {
133 | title: 'Category',
134 | dataIndex: 'category',
135 | key: 'category'
136 | }
137 |
138 | const status_col = {
139 | title: 'Status',
140 | dataIndex: 'status',
141 | key: 'status',
142 | render: ((tag) => {tag.toUpperCase()} )
143 | }
144 |
145 | if (this.state.sort == 'funding') {
146 | return ([ name_col, funding_col, batch_col, desc_col, cat_col, status_col ])
147 | }
148 | if (this.state.sort == 'exit') {
149 | return ([ name_col, exit_col, batch_col, desc_col, cat_col, status_col ])
150 | }
151 | if (this.state.sort == 'employees') {
152 | return ([ name_col, employee_col, batch_col, desc_col, cat_col, status_col ])
153 | }
154 | if (this.state.sort == 'alexa') {
155 | return ([ name_col, alexa_col, batch_col, desc_col, cat_col, status_col ])
156 | }
157 | if (this.state.sort == 'twitter') {
158 | return ([ name_col, twitter_col, batch_col, desc_col, cat_col, status_col ])
159 | }
160 | if (this.state.sort == 'tweets') {
161 | return ([ name_col, tweets_col, batch_col, desc_col, cat_col, status_col ])
162 | }
163 | if (this.state.sort == 'facebook') {
164 | return ([ name_col, facebook_col, batch_col, desc_col, cat_col, status_col ])
165 | }
166 |
167 | return ([
168 | name_col,
169 | funding_col,
170 | batch_col,
171 | desc_col,
172 | cat_col,
173 | status_col
174 | ])
175 |
176 | }
177 |
178 | const sorttype = this.state.sort || 'funding'
179 |
180 | const QueryFilterTable = category => {
181 | var categoryFilter = null
182 | if (category != "All") {
183 | categoryFilter = [['category', '==', category]]
184 | }
185 | var order = 'desc'
186 | // if (sorttype == 'alexa') { order = 'asc' }
187 | // if (sorttype == 'exit') { order = 'asc' }
188 | if (sorttype == 'exit') {
189 | if (categoryFilter != null) {
190 | categoryFilter.push(['status','==','Exited'])
191 | } else {
192 | categoryFilter = [['status','==','Exited']]
193 | }
194 | }
195 |
196 | return (
197 |
198 |
199 | { ({error, isLoading, data}) => {
200 |
201 | if (error) { console.error("Error loading users ", error)}
202 |
203 | return(
204 | record.id}
208 | onRow={(record) => ({
209 | onClick: () => { this.onRowSelect(record); }
210 | })}
211 | loading={isLoading}
212 | pagination={true} />
213 | )
214 | }}
215 |
216 |
217 | )
218 | }
219 |
220 | return (
221 |
222 |
223 | Top 100 Companies By
224 |
225 | Funding
226 | Exit Value
227 | Employee Count
228 | Alexa Rank
229 | Twitter Followers
230 | Twitter Posts
231 | Facebook Posts
232 |
233 |
234 |
235 |
236 |
237 |
244 | All} key="1">
245 | {QueryFilterTable("All")}
246 |
247 |
248 | Aerospace} key="2">
249 | {QueryFilterTable("Aerospace")}
250 |
251 |
252 | Agriculture} key="3">
253 | {QueryFilterTable("Agriculture")}
254 |
255 |
256 | AI and ML} key="4">
257 | {QueryFilterTable("AI and ML")}
258 |
259 |
260 | Blockchain} key="5">
261 | {QueryFilterTable("Blockchain")}
262 |
263 |
264 | Consumer} key="6">
265 | {QueryFilterTable("Consumer")}
266 |
267 |
268 | Dev Tools} key="7">
269 | {QueryFilterTable("Dev Tools")}
270 |
271 |
272 | Education} key="8">
273 | {QueryFilterTable("Education")}
274 |
275 |
276 | Entertainment} key="9">
277 | {QueryFilterTable("Entertainment")}
278 |
279 |
280 | Fintech} key="10">
281 | {QueryFilterTable("Fintech")}
282 |
283 |
284 | Government} key="11">
285 | {QueryFilterTable("Government")}
286 |
287 |
288 | Healthcare} key="12">
289 | {QueryFilterTable("Healthcare")}
290 |
291 |
292 | Industrial} key="13">
293 | {QueryFilterTable("Industrial")}
294 |
295 |
296 | Real Estate} key="14">
297 | {QueryFilterTable("Real Estate")}
298 |
299 |
300 | Resources} key="15">
301 | {QueryFilterTable("Resources")}
302 |
303 |
304 | Transport} key="16">
305 | {QueryFilterTable("Transport")}
306 |
307 |
308 | Other SaaS} key="17">
309 | {QueryFilterTable("Other SaaS")}
310 |
311 |
312 | Nonprofit} key="18">
313 | {QueryFilterTable("Nonprofit")}
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 | );
322 | }
323 | }
324 |
325 | export default RankingTable;
--------------------------------------------------------------------------------
/src/app/layout/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import DocumentTitle from 'react-document-title';
3 | // import { enquireScreen } from 'enquire-js';
4 | import { Layout, Menu, Icon, Card } from 'antd';
5 | import Header from '../components/Header';
6 | import './static/style';
7 |
8 |
9 | const { Content, Footer, Sider } = Layout;
10 |
11 | let isMobile = false;
12 | // enquireScreen((b) => {
13 | // isMobile = b;
14 | // });
15 |
16 | class AboutText extends React.Component {
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | About
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | We are a group of tech executives based in Silicon Valley who are interested in investing in Japanese startups.
36 | We are also, in parallel, planning a small venture capital fund called “Namaiki Ventures” which we hope to
37 | launch in early 2020. We plan to use the “JPVCDB” website to both learn about the Japanese venture market, to
38 | meet other interested investors, and to keep meeting with entrepreneurs in Japan.
39 |
40 |
41 | This website is an open source clone the very popular ycdb website and extends it
42 | for use by other organizations or, in this case, whole countries. SEO optimized, performant, opinionated
43 | React/Next/AntDesign/Firebase template app with fast loading landing page with lazy-loading of Firebase data
44 | using a simple provider class.
45 |
46 |
47 | You can learn more about this website by contacting the developers, here: Github/moflo
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ©2018-2019 Mobile Flow LLC
56 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | class About extends React.PureComponent {
64 | state = {
65 | isFirstScreen: true,
66 | isMobile,
67 | };
68 |
69 | componentDidMount() {
70 | // enquireScreen((b) => {
71 | // this.setState({
72 | // isMobile: !!b,
73 | // });
74 | // });
75 | }
76 |
77 | onEnterChange = (mode) => {
78 | this.setState({
79 | isFirstScreen: mode === 'enter',
80 | });
81 | }
82 | render() {
83 | return (
84 | [
85 | ,
86 |
,
87 |
88 | ]
89 | );
90 | }
91 | }
92 | export default About;
--------------------------------------------------------------------------------
/src/app/layout/Cohort.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import DocumentTitle from 'react-document-title';
3 | // import { enquireScreen } from 'enquire-js';
4 | import Header from '../components/Header';
5 | import BannerMin from '../components/BannerMin';
6 | import Page3 from '../components/Page3';
7 | // import Page4 from './Page4';
8 | import Footer from '../components/Footer';
9 | import './static/style';
10 | import styled from 'styled-components';
11 |
12 | let isMobile = false;
13 | // enquireScreen((b) => {
14 | // isMobile = b;
15 | // });
16 |
17 | class Cohort extends React.PureComponent {
18 | state = {
19 | id: this.props.id,
20 | isFirstScreen: true,
21 | isMobile,
22 | };
23 |
24 | componentDidMount() {
25 | // enquireScreen((b) => {
26 | // this.setState({
27 | // isMobile: !!b,
28 | // });
29 | // });
30 | }
31 |
32 | onEnterChange = (mode) => {
33 | this.setState({
34 | isFirstScreen: mode === 'enter',
35 | });
36 | }
37 |
38 | render() {
39 |
40 | const batch = this.props.id || 'W17'
41 |
42 | return (
43 | [
44 | ,
45 | ,
46 | ,
47 |
48 | ]
49 | );
50 | }
51 | }
52 | export default Cohort;
--------------------------------------------------------------------------------
/src/app/layout/Company.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import DocumentTitle from 'react-document-title';
3 | // import { enquireScreen } from 'enquire-js';
4 | import Header from '../components/Header';
5 | import BannerMin from '../components/BannerMin';
6 | import CompanyDetails from '../components/CompanyDetails';
7 | import Footer from '../components/Footer';
8 | import './static/style';
9 | import styled from 'styled-components';
10 |
11 |
12 | let isMobile = false;
13 | // enquireScreen((b) => {
14 | // isMobile = b;
15 | // });
16 |
17 |
18 | class Company extends React.PureComponent {
19 | state = {
20 | id: this.props.id,
21 | isFirstScreen: true,
22 | isMobile,
23 | };
24 |
25 | componentDidMount() {
26 | // enquireScreen((b) => {
27 | // this.setState({
28 | // isMobile: !!b,
29 | // });
30 | // });
31 | }
32 |
33 | onEnterChange = (mode) => {
34 | this.setState({
35 | isFirstScreen: mode === 'enter',
36 | });
37 | }
38 |
39 | render() {
40 | return (
41 | [
42 | ,
43 | ,
44 | ,
45 |
46 | ]
47 | );
48 | }
49 | }
50 | export default Company;
--------------------------------------------------------------------------------
/src/app/layout/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import DocumentTitle from 'react-document-title';
3 | // import { enquireScreen } from 'enquire-js';
4 | import Header from '../components/Header';
5 | import Banner from '../components/Banner';
6 | import Page1 from '../components/Page1';
7 | import Page2 from '../components/Page2';
8 | import Page3 from '../components/Page3';
9 | // import CompanyHead from './CompanyHead';
10 | import Footer from '../components/Footer';
11 | import './static/style';
12 | import styled from 'styled-components';
13 |
14 | let isMobile = false;
15 | // enquireScreen((b) => {
16 | // isMobile = b;
17 | // });
18 |
19 | class Home extends React.PureComponent {
20 | state = {
21 | isFirstScreen: true,
22 | isMobile,
23 | };
24 |
25 | componentDidMount() {
26 | // enquireScreen((b) => {
27 | // this.setState({
28 | // isMobile: !!b,
29 | // });
30 | // });
31 | }
32 |
33 | onEnterChange = (mode) => {
34 | this.setState({
35 | isFirstScreen: mode === 'enter',
36 | });
37 | }
38 | render() {
39 | return (
40 | [
41 | ,
42 | ,
43 | // ,
44 | ,
45 | ,
46 | ,
47 | // ,
48 |
49 | ]
50 | );
51 | }
52 | }
53 | export default Home;
--------------------------------------------------------------------------------
/src/app/layout/Login.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {Row, Col, Form, Input, Icon, Button, Alert, notification} from 'antd';
3 | import { doEmailSignin, redirectIfAuthenticated } from "../lib/auth";
4 | import Link from 'next/link'
5 | import styled from 'styled-components';
6 |
7 | const LogoContainer = styled.div`
8 | text-align: center;
9 | margin-bottom: 30px;
10 | `
11 |
12 | const LoginContainer = styled.div`
13 | align-self: center;
14 | max-width: 500px;
15 | margin: 0 auto;
16 | background-color: #FFF;
17 | border-radius: 5px;
18 | padding: 20px;
19 | `
20 |
21 | const BodyWrapper = styled.div`
22 | background: linear-gradient(to left top,#bf0216,#f44e5f);
23 | min-height: 600px;
24 | height: 100vh;
25 | width: 100%;
26 | `
27 |
28 | const FooterContainer = styled.div`
29 | text-align: center;
30 | margin-top: 30px;
31 | color: #FFF;
32 |
33 | a {
34 | color: #FFF;
35 | text-decoration: underline;
36 | }
37 | `
38 |
39 | const FormItem = Form.Item;
40 |
41 | // if (!firebase.apps.length) {
42 | // firebase.initializeApp(firebaseConfig);
43 | // }
44 |
45 | class LoginForm extends React.Component {
46 |
47 | // static async getInitialProps () {
48 | // return {}
49 | // }
50 |
51 | handleSubmit = (e) => {
52 | e.preventDefault();
53 | const {history} = this.props;
54 | this.props.form.validateFields((err, values) => {
55 | if (!err) {
56 |
57 | console.log(`handleSubmit: `+JSON.stringify(values) )
58 |
59 | // doGooglePopup().then((result) => {
60 | // console.log("login result", result)
61 | // redirectIfAuthenticated(result)
62 | // })
63 |
64 | doEmailSignin(values.email,values.password).then((result) => {
65 | console.log("login result", result)
66 | redirectIfAuthenticated()
67 | })
68 |
69 | }
70 | });
71 | }
72 |
73 | render() {
74 | const { getFieldDecorator } = this.props.form;
75 |
76 | return (
77 |
78 |
79 |
80 |
81 |
82 |
83 |
104 |
105 |
106 |
107 | Home
108 |
109 |
110 |
111 |
112 | );
113 | }
114 | }
115 |
116 | class Login extends React.Component {
117 |
118 | render() {
119 | const LoginFormWrapped = Form.create()(LoginForm);
120 |
121 | return (
122 |
123 |
124 |
125 | );
126 | }
127 | }
128 |
129 | // export default Form.create()(Login);
130 | export default Login;
131 |
--------------------------------------------------------------------------------
/src/app/layout/Ranking.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import DocumentTitle from 'react-document-title';
3 | // import { enquireScreen } from 'enquire-js';
4 | import Header from '../components/Header';
5 | import BannerMin from '../components/BannerMin';
6 | import RankingTable from '../components/RankingTable';
7 | // import Page4 from './Page4';
8 | import Footer from '../components/Footer';
9 | import './static/style';
10 | import styled from 'styled-components';
11 |
12 | let isMobile = false;
13 | // enquireScreen((b) => {
14 | // isMobile = b;
15 | // });
16 |
17 | class Ranking extends React.PureComponent {
18 | state = {
19 | id: this.props.id,
20 | isFirstScreen: true,
21 | isMobile,
22 | };
23 |
24 | componentDidMount() {
25 | // enquireScreen((b) => {
26 | // this.setState({
27 | // isMobile: !!b,
28 | // });
29 | // });
30 | }
31 |
32 | onEnterChange = (mode) => {
33 | this.setState({
34 | isFirstScreen: mode === 'enter',
35 | });
36 | }
37 |
38 | render() {
39 |
40 | const sort = this.props.id || 'funding'
41 |
42 | return (
43 | [
44 | ,
45 | ,
46 | ,
47 |
48 | ]
49 | );
50 | }
51 | }
52 | export default Ranking;
--------------------------------------------------------------------------------
/src/app/layout/static/default.less:
--------------------------------------------------------------------------------
1 | // @import "~antd/lib/style/themes/default.less";
2 | @import "../../asserts/styles.less";
3 | @border-color: rgba(229, 231, 235, 100);
4 | @padding-space: 144px;
5 | @site-text-color: #314659;
--------------------------------------------------------------------------------
/src/app/layout/static/footer.less:
--------------------------------------------------------------------------------
1 | @import './default.less';
2 |
3 |
4 | footer.dark {
5 | background-color: #d0021b;
6 | color: rgba(255, 255, 255, 0.65);
7 | a {
8 | color: rgba(255, 255, 255, 0.9);
9 | }
10 | h2 {
11 | color: rgba(255, 255, 255, 1);
12 | & > span {
13 | color: rgba(255, 255, 255, 1);
14 | }
15 | }
16 | .bottom-bar {
17 | border-top: 1px solid rgba(255, 255, 255, 0.25);
18 | overflow: hidden;
19 | }
20 | }
21 |
22 | footer {
23 | border-top: 1px solid @border-color;
24 | clear: both;
25 | font-size: 14px;
26 | background: #fff;
27 | position: relative;
28 | z-index: 100;
29 | color: @site-text-color;
30 | box-shadow: 0 1000px 0 1000px #fff;
31 | .ant-row {
32 | text-align: center;
33 | .footer-center {
34 | display: inline-block;
35 | text-align: left;
36 | > h2 {
37 | font-size: 16px;
38 | margin: 0 auto 24px;
39 | font-weight: 500;
40 | position: relative;
41 |
42 | > .title-icon {
43 | width: 27px;
44 | margin-right: 16px;
45 | }
46 | > .anticon {
47 | font-size: 16px;
48 | position: absolute;
49 | left: -22px;
50 | top: 3px;
51 | color: #aaa;
52 | }
53 | }
54 | > div {
55 | margin: 12px 0;
56 | }
57 | > span i {
58 | margin-right: 10px;
59 | }
60 | }
61 | }
62 | .footer-wrap {
63 | position: relative;
64 | padding: 86px @padding-space 93px @padding-space;
65 | }
66 | .bottom-bar {
67 | background-color: #000;
68 | border-top: 1px solid @border-color;
69 | text-align: right;
70 | padding: 16px @padding-space;
71 | margin: 0;
72 | line-height: 32px;
73 | a {
74 | color: rgba(255, 255, 255, 0.65);
75 | &:hover {
76 | color: #fff;
77 | }
78 | }
79 | .translate-button {
80 | text-align: left;
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/app/layout/static/header.less:
--------------------------------------------------------------------------------
1 | @import './default.less';
2 | @header-height: 80px;
3 | #header {
4 | position: fixed;
5 | z-index: 999;
6 | background: rgba(255, 255, 255, 1.0);
7 | border-bottom: 1px solid transparent;
8 | transition: border .5s cubic-bezier(0.455, 0.03, 0.515, 0.955), background .5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
9 | height: @header-height;
10 | padding: 0 48px;
11 | width: 100%;
12 | &.home-nav-bottom {
13 | background: rgba(255, 255, 255, 0.9);
14 | border-bottom-color: #ebedee;
15 | .search {
16 | border-left-color: #ebedee;
17 | }
18 | a {
19 | color: @text-color;
20 | }
21 | }
22 | .header-link {
23 | color: @text-color;
24 | }
25 | .ant-menu-item-active .header-link {
26 | color: @primary-color;
27 | }
28 | }
29 |
30 | #logo {
31 | float: left;
32 | height: 80px;
33 | line-height: 80px;
34 | }
35 |
36 | #logo img {
37 | height: 50px;
38 | line-height: 80px;
39 | width: 50px;
40 | margin-right: 8px;
41 | margin-left: 40px;
42 | }
43 |
44 | #logo span {
45 | float: right;
46 | // font-size: 16px;
47 | // font-family: 'Raleway', 'Hiragino Sans GB', sans-serif;
48 | height: 80px;
49 | line-height: 80px;
50 | text-transform: uppercase;
51 | }
52 |
53 | .header-search-select {
54 | height: 40px;
55 | width: 200px;
56 | color: #333;
57 | }
58 |
59 | .header-search-icon {
60 | margin-left: -26px;
61 | }
62 |
63 | .header-search-select .ant-select {
64 | font-size: 14px;
65 | margin-top: -3px;
66 | width: 200px;
67 | color: "#333"
68 | }
69 |
70 | #search-box .ant-select-selection {
71 | border: 0;
72 | box-shadow: none;
73 | }
74 |
75 | #search-box .ant-input {
76 | border: 0;
77 | box-shadow: none;
78 | }
79 |
80 | .header-lang-button,
81 | .version {
82 | float: right;
83 | margin-top: 29px;
84 | margin-left: 10px;
85 | }
86 |
87 | .header-lang-button {
88 | color: @text-color;
89 | border-color: @border-color-base;
90 | }
91 |
92 | .version {
93 | margin-left: 16px;
94 | }
95 |
96 | #nav {
97 | border: 0;
98 | float: right;
99 | // font-size: 14px;
100 | // font-family: Lato, @font-family;
101 | }
102 |
103 | #nav li {
104 | height: @header-height;
105 | line-height: @header-height;
106 | min-width: 72px;
107 | text-align: center;
108 | border-bottom-width: 0px; // was 3px;
109 | &.ant-menu-item-selected a {
110 | color: @primary-color;
111 | font-weight: bold;
112 | }
113 | }
114 |
115 | .component-select {
116 | &.ant-select-dropdown {
117 | border: 0;
118 | border-radius: 0;
119 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.25);
120 | font-size: 14px;
121 | }
122 | .ant-select-dropdown-menu {
123 | max-height: 200px;
124 | }
125 | .ant-select-dropdown-menu-item {
126 | border-radius: 0 !important;
127 | }
128 | .ant-component-decs {
129 | font-size: 12px;
130 | position: absolute;
131 | color: #aaa;
132 | right: 16px;
133 | }
134 | }
135 |
136 | #header .header-lang-button {
137 | color: #fff;
138 | border-color: #fff;
139 | font-size: 30px;
140 | vertical-align: middle;
141 | }
142 | #header .ant-select-selection,
143 | #header .ant-menu {
144 | background: transparent;
145 | }
146 | #header .ant-select-search__field {
147 | color: #eee;
148 | }
149 | #header .ant-select-arrow {
150 | color: #fff;
151 | }
152 | #header .ant-select-selection__placeholder {
153 | color: rgba(255,255,255,0.57);
154 | }
155 | #header.home-nav-white .ant-select-search__field {
156 | color: rgba(0, 0, 0, 0.65);
157 | }
158 | #header.home-nav-white .ant-select-selection__placeholder {
159 | color: rgb(204, 204, 204);
160 | }
161 | #header.home-nav-white {
162 | background: rgba(255, 255, 255, 0.91);
163 | border-bottom-color: #ebedee;
164 | }
165 | .home-nav-white #search-box {
166 | border-left-color: #ebedee;
167 | }
168 | .home-nav-white #nav li {
169 | color: rgba(0, 0, 0, 0.65);
170 | }
171 | #header.home-nav-white .header-lang-button:not(:hover) {
172 | color: rgba(0, 0, 0, 0.65);
173 | border-color: #d9d9d9;
174 | }
175 | #header.home-nav-white .version > .ant-select-selection {
176 | color: rgba(0, 0, 0, 0.65);
177 | }
178 | #header.home-nav-white .version > .ant-select-selection:not(:hover) {
179 | border-color: #d9d9d9;
180 | }
181 | #header.home-nav-white .version .ant-select-arrow {
182 | color: rgba(0, 0, 0, 0.45);
183 | }
184 | .nav-phone-icon:before {
185 | background: #eee;
186 | box-shadow: 0 7px 0 0 #eee, 0 14px 0 0 #eee;
187 | }
188 | .home-nav-white .nav-phone-icon:before {
189 | background: #777;
190 | box-shadow: 0 7px 0 0 #777, 0 14px 0 0 #777;
191 | }
192 |
193 | #nav li {
194 | color: #eee;
195 | transition: all 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
196 | }
197 | #nav a.header-link {
198 | color: #fff;
199 | }
200 | .home-nav-white #nav a.header-link {
201 | color: rgba(0, 0, 0, .65);
202 | }
--------------------------------------------------------------------------------
/src/app/layout/static/home.less:
--------------------------------------------------------------------------------
1 | @import './default.less';
2 | .banner-wrapper {
3 | position: relative;
4 | width: 100%;
5 | overflow: hidden;
6 | background: url("/static/header-hero3.jpg") no-repeat center / cover;
7 | }
8 | .banner-text-wrapper {
9 | position: relative;
10 | // left: 13%;
11 | top: 40%;
12 | color: #fff;
13 | text-align: center;
14 | font-family: 'Roboto Slab';
15 | }
16 | .banner-text-wrapper h2 {
17 | font-size: 50px;
18 | font-weight: bold;
19 | font-family: 'Avenir Next', Sans-Serif;
20 | white-space: nowrap;
21 | color: #fff;
22 | margin-bottom: 8px;
23 | }
24 | .banner-text-wrapper h2 p {
25 | color: #ff3171;
26 | display: inline-block;
27 | margin: 0;
28 | }
29 | .banner-text-wrapper .line {
30 | width: 0.8px;
31 | height: 76px;
32 | position: absolute;
33 | background: rgba(255, 255, 255, .44);
34 | background-image: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03));
35 | background-image: -moz-linear-gradient(top, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03));
36 | background-image: -ms-linear-gradient(top, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03));
37 | background-image: -o-linear-gradient(top, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03));
38 | top: 16px;
39 | right: -25px;
40 | }
41 | .banner-text-wrapper > p {
42 | margin: 10px auto 24px;
43 | font-size: 16px;
44 | }
45 |
46 | .banner-text-wrapper .banner-search {
47 | height: 40px;
48 | color: #000;
49 | width: 500px;
50 | margin-bottom: 20px;
51 | .ant-select {
52 | background-color: #ff3171;
53 | -webkit-border-radius: 4px;
54 | -moz-border-radius: 4px;
55 | border-radius: 4px;
56 | }
57 | }
58 |
59 | .banner-text-wrapper .banner-button-dark {
60 | height: 40px;
61 | background-color: #000;
62 | color: #fff;
63 | margin-right: 5px;
64 | border-color: #000;
65 | -webkit-box-shadow: 10px 10px 16px 0px rgba(0,0,0,0.25);
66 | -moz-box-shadow: 10px 10px 16px 0px rgba(0,0,0,0.25);
67 | box-shadow: 10px 10px 16px 0px rgba(0,0,0,0.25);
68 | -webkit-border-radius: 4px;
69 | -moz-border-radius: 4px;
70 | border-radius: 4px;
71 | }
72 |
73 | .banner-text-wrapper .banner-button-light {
74 | height: 40px;
75 | background-color: #fff;
76 | margin-left: 5px;
77 | -webkit-border-radius: 4px;
78 | -moz-border-radius: 4px;
79 | border-radius: 4px;
80 | }
81 |
82 | .home-cohort-tabs > .ant-tabs-tab {
83 | text-align: left;
84 | }
85 |
86 |
87 | .banner-text-wrapper .start-button {
88 | @start-button-color: tint(@primary-color, 20%);
89 | margin-bottom: 24px;
90 | text-align: center;
91 | > a {
92 | display: inline-block;
93 | height: 40px;
94 | padding: 0 16px;
95 | font-weight: 500;
96 | border-radius: @border-radius-base;
97 | border: 1px solid @start-button-color;
98 | text-decoration: none;
99 | color: #0ae;
100 | font-size: 15px;
101 | background: transparent;
102 | transition: all .45s;
103 | text-align: center;
104 | line-height: 36px;
105 | margin-left: 8px;
106 | &:hover {
107 | color: #fff;
108 | background: @start-button-color;
109 | }
110 | }
111 | }
112 |
113 | .banner-text-wrapper .github-btn {
114 | float: right;
115 | line-height: 14px;
116 | text-align: left;
117 |
118 | a:hover {
119 | color: #333;
120 | }
121 | }
122 |
123 | .down {
124 | text-align: center;
125 | position: absolute;
126 | bottom: 30px;
127 | color: rgba(255, 255, 255, .75);
128 | left: 50%;
129 | margin-left: -7px;
130 | animation: upDownMove 1.2s ease-in-out infinite alternate;
131 | }
132 |
133 | .page {
134 | min-height: 400px;
135 | height: 50vh;
136 | }
137 |
138 | .content-wrapper {
139 | width: 100%;
140 | max-width: 1500px;
141 | margin: auto;
142 | overflow: hidden;
143 | background: #fff;
144 |
145 | .image-wrapper {
146 | width: 65%;
147 | float: left;
148 | position: relative;
149 | }
150 |
151 | .text-wrapper {
152 | width: 35%;
153 | float: left;
154 | position: relative;
155 | top: 20%;
156 | left: -30px;
157 | font-family: Lato, @font-family;
158 | z-index: 1;
159 | }
160 | .text-wrapper h2,
161 | .text-wrapper-bottom h2 {
162 | font-size: 32px;
163 | color: @text-color;
164 | font-weight: normal;
165 | white-space: nowrap;
166 | }
167 | .text-wrapper p {
168 | margin: 10px 0 20px;
169 | font-size: 16px;
170 | line-height: 28px;
171 | color: #999;
172 | }
173 | .left-text {
174 | padding-left: 10%;
175 | }
176 | }
177 |
178 | .image1 {
179 | background: url("https://t.alipayobjects.com/images/T1Ch8kXfpdXXXXXXXX.png") no-repeat right / 841px;
180 | height: 511px;
181 | top: 50%;
182 | margin-top: -256px;
183 | }
184 | .image2 {
185 | background: url("https://t.alipayobjects.com/images/T1r5RkXotXXXXXXXXX.png") no-repeat left / 800px;
186 | height: 474px;
187 | top: 50%;
188 | margin-top: -266px;
189 | }
190 | .image3 {
191 | padding-right: 10%;
192 | background: url("https://t.alipayobjects.com/images/T1nx0kXdFfXXXXXXXX.png") no-repeat right / 639px;
193 | background-origin: content-box;
194 | height: 500px;
195 | top: 50%;
196 | margin-top: -250px;
197 | }
198 | .image4 {
199 | background: url("https://t.alipayobjects.com/images/T1lyJkXg4aXXXXXXXX.png") no-repeat center / 615px;
200 | height: 364px;
201 | margin: auto;
202 | }
203 |
204 | .text-wrapper-bottom {
205 | text-align: center;
206 | margin: 10% auto 40px;
207 | }
208 | .text-wrapper-bottom p {
209 | margin: 20px auto;
210 | font-size: 16px;
211 | line-height: 28px;
212 | color: #999;
213 | }
214 |
215 | @keyframes upDownMove {
216 | to {
217 | transform: translateY(10px);
218 | }
219 | }
--------------------------------------------------------------------------------
/src/app/layout/static/responsive.less:
--------------------------------------------------------------------------------
1 | @import "./default.less";
2 | .nav-phone-icon {
3 | display: none;
4 | position: absolute;
5 | right: 30px;
6 | top: 32px;
7 | z-index: 1;
8 | width: 16px;
9 | height: 22px;
10 | cursor: pointer;
11 | }
12 |
13 | @media only screen and (min-width: 0) and (max-width: 1280px) {
14 | .en-us #search-box {
15 | display: none;
16 | }
17 | }
18 |
19 | @media only screen and (min-width: 0) and (max-width: 1180px) {
20 | .zh-cn #search-box {
21 | display: none;
22 | }
23 | }
24 |
25 | @media only screen and (min-width: 0) and (max-width: 992px) {
26 | #search-box {
27 | display: none;
28 | }
29 | .code-boxes-col-2-1, .code-boxes-col-1-1 {
30 | float: none;
31 | width: 100%;
32 | }
33 | .preview-image-boxes {
34 | margin: 0 !important;
35 | float: none;
36 | width: 100%;
37 | }
38 | .preview-image-box {
39 | padding-left: 0;
40 | margin: 10px 0;
41 | }
42 |
43 | a#logo {
44 | width: 150px;
45 | margin-left: auto;
46 | margin-right: auto;
47 | float: none;
48 | display: block;
49 | }
50 |
51 | .banner-entry {
52 | position: relative;
53 | top: 30px;
54 | left: 0;
55 | text-align: center;
56 | }
57 |
58 | .image-wrapper {
59 | display: none;
60 | }
61 |
62 | .banner-wrapper {
63 | background-position: 40%;
64 | }
65 |
66 | .content-wrapper .text-wrapper {
67 | float: none;
68 | text-align: center;
69 | left: 0;
70 | width: 100%;
71 | padding: 0;
72 | > p {
73 | max-width: 100% !important;
74 | padding: 0 40px;
75 | }
76 | }
77 |
78 | .content-wrapper.page {
79 | min-height: 300px;
80 | height: 300px;
81 | }
82 |
83 | .banner-text-wrapper {
84 | left: 50%;
85 | transform: translateX(-50%);
86 | text-align: center;
87 | .start-button {
88 | text-align: center;
89 | > a {
90 | margin: 0 4px;
91 | }
92 | }
93 | .github-btn {
94 | text-align: center;
95 | float: none;
96 | display: inline-block;
97 | }
98 | .line {
99 | display: none;
100 | }
101 | }
102 |
103 | button.lang {
104 | display: block;
105 | margin: 29px auto 16px;
106 | color: @text-color;
107 | border-color: @text-color;
108 | }
109 |
110 | div.version {
111 | display: block;
112 | margin: 29px auto 16px;
113 | & > .ant-select-selection {
114 | color: @text-color;
115 | &:not(:hover) {
116 | border-color: @text-color;
117 | }
118 | }
119 | }
120 |
121 | .popover-menu {
122 | width: 300px;
123 | button.lang {
124 | margin: 16px auto;
125 | float: none;
126 | }
127 | div.version {
128 | margin: 32px auto 16px;
129 | float: none;
130 | }
131 | .ant-popover-inner {
132 | overflow: hidden;
133 | &-content {
134 | padding: 0;
135 | }
136 | }
137 | }
138 |
139 | ul#nav,
140 | ul#nav li {
141 | width: 100%;
142 | font-size: 14px;
143 | }
144 |
145 | ul#nav li, {
146 | line-height: 60px;
147 | height: 60px;
148 | padding: 0 !important;
149 | border: 0;
150 | color: #333;
151 | .header-link{
152 | color: #333;
153 | }
154 | }
155 |
156 | .toc {
157 | display: none;
158 | }
159 |
160 | .nav-phone-icon {
161 | display: block;
162 | }
163 |
164 | .nav-phone-icon:before {
165 | content: "";
166 | display: block;
167 | border-radius: 2px;
168 | width: 16px;
169 | height: 2px;
170 | background: #777;
171 | box-shadow: 0 6px 0 0 #777, 0 12px 0 0 #777;
172 | position: absolute;
173 | }
174 |
175 | .main {
176 | height: calc(100% - 86px);
177 | }
178 |
179 | .aside-container {
180 | float: none;
181 | width: auto;
182 | padding-bottom: 30px;
183 | border-bottom: 1px solid #e9e9e9;
184 | border-right: 0;
185 | margin-bottom: 30px;
186 | }
187 |
188 | .main-container {
189 | padding-left: 16px;
190 | padding-right: 16px;
191 | margin-right: 0;
192 | > .markdown > * {
193 | width: 100% !important;
194 | }
195 | }
196 |
197 | .main-wrapper {
198 | width: 100%;
199 | border-radius: 0;
200 | margin: 0;
201 | }
202 |
203 | footer {
204 | text-align: center;
205 | ul li {
206 | float: none;
207 | width: auto;
208 | > h2 .anticon {
209 | display: none;
210 | }
211 | }
212 | }
213 | }
--------------------------------------------------------------------------------
/src/app/layout/static/style.js:
--------------------------------------------------------------------------------
1 | import './header.less';
2 | import './home.less';
3 | import './footer.less';
4 | import './responsive.less';
--------------------------------------------------------------------------------
/src/app/lib/AuthStateProvider.js:
--------------------------------------------------------------------------------
1 | // wrapper for Firebase authentication with lazy-loading
2 |
3 | import React from 'react'
4 | import firebaseManager from './firebaseManager'
5 |
6 |
7 | class AuthStateProvider extends React.Component {
8 |
9 | state = {
10 | isLoading: true,
11 | error: null,
12 | auth: null,
13 | }
14 |
15 | componentDidMount() {
16 | this.unsubscribe = firebaseManager.sharedInstance.firebase().auth()
17 | .onAuthStateChanged(this.handleAuth, this.handleError)
18 | }
19 |
20 | componentWillUnmount() {
21 | if (this.unsubscribe) {
22 | this.unsubscribe()
23 | }
24 | }
25 |
26 | handleAuth = auth => {
27 | this.setState({
28 | isLoading: false,
29 | auth,
30 | error: null,
31 | })
32 | }
33 |
34 | handleError = error => {
35 | this.setState({
36 | isLoading: false,
37 | auth: null,
38 | error,
39 | })
40 | }
41 |
42 | render() {
43 | return this.props.children(this.state)
44 | }
45 |
46 | }
47 |
48 | export default AuthStateProvider
49 |
--------------------------------------------------------------------------------
/src/app/lib/FirebaseProvider.js:
--------------------------------------------------------------------------------
1 | // Component to provide a pagination result of firebase collection data
2 | // Based on (react-firestore)[https://github.com/green-arrow/react-firestore/blob/master/src/FirestoreCollection.js]
3 |
4 | // TODO: Need to add pagination capabilities, including record count
5 |
6 | import React from 'react'
7 | import firebaseManager from './firebaseManager'
8 | import PropTypes from 'prop-types';
9 |
10 | class FirebaseProvider extends React.Component {
11 | static propTypes = {
12 | path: PropTypes.string.isRequired,
13 | sort: PropTypes.string,
14 | limit: PropTypes.number,
15 | filter: PropTypes.oneOfType([
16 | PropTypes.arrayOf(
17 | PropTypes.oneOfType([
18 | PropTypes.string,
19 | PropTypes.number,
20 | PropTypes.object,
21 | ]),
22 | ),
23 | PropTypes.arrayOf(PropTypes.array),
24 | ]),
25 | children: PropTypes.func,
26 | render: PropTypes.func,
27 | };
28 |
29 |
30 | state = {
31 | isLoading: true,
32 | data: [],
33 | error: null,
34 | snapshot: null,
35 | };
36 |
37 | componentDidMount() {
38 | this.setupFirestoreListener(this.props);
39 | }
40 |
41 | componentWillUnmount() {
42 | this.handleUnsubscribe();
43 | }
44 |
45 | componentWillReceiveProps(nextProps) {
46 | if (
47 | nextProps.path !== this.props.path ||
48 | nextProps.sort !== this.props.sort ||
49 | nextProps.limit !== this.props.limit // ||
50 | // !deepEqual(nextProps.filter, this.props.filter)
51 | ) {
52 | this.handleUnsubscribe();
53 |
54 | this.setState({ isLoading: true }, () =>
55 | this.setupFirestoreListener(this.props),
56 | );
57 | }
58 | }
59 |
60 | handleUnsubscribe() {
61 | if (this.unsubscribe) {
62 | this.unsubscribe();
63 | }
64 | }
65 |
66 | setupFirestoreListener = props => {
67 | const firestoreDatabase = firebaseManager.sharedInstance.firestore()
68 | const { path, ...queryProps } = props;
69 | const collectionRef = firestoreDatabase.collection(path);
70 | const query = this.buildQuery(collectionRef, queryProps);
71 |
72 | this.unsubscribe = query.onSnapshot(
73 | this.handleOnSnapshotSuccess,
74 | this.handleOnSnapshotError,
75 | );
76 | };
77 |
78 | handleOnSnapshotSuccess = snapshot => {
79 | if (snapshot) {
80 | this.setState({
81 | isLoading: false,
82 | data: snapshot.docs.map(doc => ({
83 | id: doc.id,
84 | ...doc.data(),
85 | })),
86 | error: null,
87 | snapshot,
88 | });
89 | }
90 | };
91 |
92 | handleOnSnapshotError = error => {
93 | this.setState({
94 | isLoading: false,
95 | data: [],
96 | error,
97 | snapshot: null,
98 | });
99 | };
100 |
101 | buildQuery = (collectionRef, queryProps) => {
102 | const { sort, limit, filter } = queryProps;
103 | let query = collectionRef;
104 |
105 | if (sort) {
106 | sort.split(',').forEach(sortItem => {
107 | const [field, order] = sortItem.split(':');
108 |
109 | query = query.orderBy(field, order);
110 | });
111 | }
112 |
113 | if (limit) {
114 | query = query.limit(limit);
115 | }
116 |
117 | if (filter) {
118 | //if filter is array of array, build the compound query
119 | if (Array.isArray(filter[0])) {
120 | filter.forEach(clause => {
121 | query = query.where(...clause);
122 | });
123 | } else {
124 | //build the simple query
125 | query = query.where(...filter);
126 | }
127 | }
128 |
129 | return query;
130 | };
131 |
132 | render() {
133 | const { children, render } = this.props;
134 |
135 | if (render) return render(this.state);
136 |
137 | if (typeof children === 'function') return children(this.state);
138 |
139 | return null;
140 | }
141 | }
142 |
143 | export default FirebaseProvider;
144 |
--------------------------------------------------------------------------------
/src/app/lib/auth.js:
--------------------------------------------------------------------------------
1 | import redirect from "./redirect";
2 | import firebaseManager from './firebaseManager'
3 |
4 | export const doGooglePopup = () => firebaseManager.sharedInstance.handleLogin()
5 |
6 | export const doEmailSignin = (email,pass) => firebaseManager.sharedInstance.handleEmailLogin(email,pass)
7 |
8 | export const isAuthenticated = ctx => firebaseManager.sharedInstance.isLoggedIn()
9 |
10 | export const redirectIfAuthenticated = ctx => {
11 | if (isAuthenticated(ctx)) {
12 | redirect("/dashboard", ctx);
13 | return true;
14 | }
15 | return false;
16 | };
17 |
18 | export const redirectIfNotAuthenticated = ctx => {
19 | if (!isAuthenticated(ctx)) {
20 | redirect("/login", ctx);
21 | return true;
22 | }
23 | return false;
24 | };
25 |
--------------------------------------------------------------------------------
/src/app/lib/createCompany.js:
--------------------------------------------------------------------------------
1 | import firebaseManager from './firebaseManager'
2 | const slugify = require('slugify')
3 |
4 | const createCompany = values => {
5 |
6 | // ReactGA.event({
7 | // category: 'Message',
8 | // action: 'Create messasge',
9 | // })
10 |
11 | const id = slugify(values.name, {lower: true})
12 |
13 | let company = {
14 | id,
15 | name: values.name,
16 | batch: values.batch,
17 | description: values.description,
18 | category: values.category,
19 | exit: 0,
20 | twitter: 0,
21 | alexa: 343,
22 | growth: 0,
23 | founderCount: 1,
24 | facebook: 0,
25 | domains: 0,
26 | logo: "",
27 | fundingString: "",
28 | www: values.www,
29 | address: values.address,
30 | funding: values.funding,
31 | status: values.status,
32 | linkedin: 0,
33 | ticker: "",
34 | landingpage: values.landingpage,
35 | tweets: 0,
36 | employees: 1,
37 | hqLocation: "",
38 | coordinates: {
39 | lng: 139.7454,
40 | lat: 35.6586
41 | },
42 | founderBackground: [
43 | ],
44 | links: 0,
45 | performance: {
46 | funding:0.0,fundingCat:100,
47 | employees:0.0,employeesCat:100.0,
48 | growth:0.0,growthCat:100.0,
49 | alexa:0.0,alexaCat:100.0,
50 | twitter:0.0,twitterCat:100
51 | }
52 | }
53 |
54 | return firebaseManager.sharedInstance.firestore()
55 | .collection('companies')
56 | .doc(id)
57 | .set(firebaseManager.sharedInstance.prepareDocForCreate(values))
58 | .then( () => values)
59 | .catch( error => {
60 | conslole.log(`Error trying to create the company: ${error.message}`)
61 | alert(`Error trying to create the company: ${error.message}`)
62 | })
63 | }
64 |
65 | export default createCompany
66 |
--------------------------------------------------------------------------------
/src/app/lib/createMessage.js:
--------------------------------------------------------------------------------
1 | import firebaseManager from './firebaseManager'
2 |
3 | const createMessage = values => {
4 |
5 | // ReactGA.event({
6 | // category: 'Message',
7 | // action: 'Create messasge',
8 | // })
9 |
10 |
11 | return firebaseManager.sharedInstance.firestore()
12 | .collection('messages')
13 | .add(firebaseManager.sharedInstance.prepareDocForCreate(values))
14 | .then( () => values)
15 | .catch( error => {
16 | alert(`Whoops, couldn't create the post: ${error.message}`)
17 | })
18 | }
19 |
20 | export default createMessage
21 |
--------------------------------------------------------------------------------
/src/app/lib/emptyMessage.js:
--------------------------------------------------------------------------------
1 | import firebaseManager from './firebaseManager'
2 |
3 | const emptyMessage = values => {
4 |
5 | const empty = {
6 | id,
7 | title,
8 | body,
9 | priority: 1
10 | }
11 |
12 | return firebaseManager.sharedInstance.prepareDocForCreate(Object.assign(value, empty))
13 |
14 | }
15 |
16 | export default emptyMessage
17 |
--------------------------------------------------------------------------------
/src/app/lib/firebaseManager.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app'
2 | import 'firebase/auth'
3 | import 'firebase/firestore'
4 | import 'firebase/storage'
5 | import clientCredentials from '../credentials/client'
6 |
7 |
8 | export default class firebaseManager {
9 | static sharedInstance = firebaseManager.sharedInstance == null ? new firebaseManager() : this.sharedInstance
10 |
11 | firebase() {
12 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
13 |
14 | return firebase
15 | }
16 |
17 | firestore() {
18 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
19 |
20 | var db = firebase.firestore()
21 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
22 |
23 | return db
24 | }
25 |
26 | handleLogin = event => {
27 | return new Promise((resolve, reject) => {
28 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
29 |
30 | firebase.auth().signInWithPopup(new firebase.auth.GoogleAuthProvider())
31 |
32 | var unsubscribe = firebase.auth().onAuthStateChanged(user => {
33 | if (user) {
34 |
35 | resolve(user)
36 |
37 | } else {
38 |
39 | resolve()
40 | }
41 |
42 | if (!unsubscribe) unsubscribe()
43 | })
44 |
45 | })
46 | }
47 |
48 | handleEmailLogin = (email,pass) => {
49 | return new Promise((resolve, reject) => {
50 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
51 |
52 | firebase.auth().signInWithEmailAndPassword(email, pass)
53 | .then((result) => {
54 | // console.log(`handleEmailLogin OK`+JSON.stringify(result))
55 | resolve()
56 | })
57 | .catch(function(error) {
58 | // console.log(`handleEmailLogin Error`+JSON.stringify(error))
59 | resolve(error)
60 | })
61 |
62 | })
63 | }
64 |
65 | isLoggedIn() {
66 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
67 |
68 | let user = firebase.auth().currentUser
69 |
70 | return (user != null)
71 | }
72 |
73 | getUserDetails() {
74 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
75 |
76 | let user = firebase.auth().currentUser || {}
77 |
78 | let userObj = {
79 | name: user.displayName || "MoFlo User",
80 | avatarURL: user.photoURL || "https://fillmurray.com/64/64",
81 | email: user.email || "demo@moflo.me"
82 | }
83 |
84 | return userObj
85 | }
86 |
87 | prepareDocForCreate = doc => {
88 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
89 |
90 | // timestamps
91 | doc.createdBy = firebase.auth().currentUser ? firebase.auth().currentUser.uid : null
92 | doc.createdAt = firebase.firestore.Timestamp.now()
93 |
94 | return doc
95 | }
96 |
97 | prepareDocForUpdate = doc => {
98 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
99 |
100 | // timestamps
101 | doc.updatedBy = firebase.auth().currentUser ? firebase.auth().currentUser.uid : null
102 | doc.updatedAt = firebase.firestore.Timestamp.now()
103 |
104 | // don't save the id as part of the document
105 | delete doc.id
106 |
107 | // don't save values that start with an underscore (these are calculated by the backend)
108 | Object.keys(doc).forEach( key => {
109 | if (key.indexOf('_') === 0) {
110 | delete doc[key]
111 | }
112 | })
113 |
114 | return doc
115 | }
116 |
117 |
118 | /*
119 | file : JS File reference
120 | name : Override existing file name, needs and extension ('test.jpg')
121 | onProgress: function to update progress (percent, task)
122 | onError: function to respond to upload error (error, task)
123 | */
124 |
125 | uploadFile = (file,name,onProgress,onError) => {
126 | return new Promise((resolve, reject) => {
127 | if (!firebase.apps.length) firebase.initializeApp(clientCredentials)
128 |
129 | var storageRef = firebase.storage().ref('images')
130 |
131 | let filenameToUse = name || file.name || 'upload.png'
132 |
133 | console.log(`uploadfile, filename: ${filenameToUse}`)
134 | console.log(`uploadfile, file type: ${file instanceof File}`)
135 |
136 | // var blob = new Blob([file], { type: "image/jpeg" });
137 |
138 | const task = storageRef.child(filenameToUse).put(file)
139 |
140 | task.on(
141 | 'state_changed',
142 | snapshot =>
143 | onProgress &&
144 | onProgress(
145 | Math.round(100 * snapshot.bytesTransferred / snapshot.totalBytes),
146 | task
147 | ),
148 | error => {
149 | onError && onError(error, task)
150 | resolve()
151 | },
152 | () => {
153 | // Return download URL
154 | storageRef.child(filenameToUse).getDownloadURL()
155 | .then(url => resolve( url) );
156 | }
157 | );
158 |
159 | })
160 | }
161 |
162 | }
--------------------------------------------------------------------------------
/src/app/lib/redirect.js:
--------------------------------------------------------------------------------
1 | import Router from "next/router";
2 |
3 | export default (target, ctx = {}) => {
4 | if (ctx.res) {
5 | // server
6 | // 303: "See other"
7 | ctx.res.writeHead(303, { Location: target });
8 | ctx.res.end();
9 | } else {
10 | // In the browser, we just pretend like this never even happened ;)
11 | Router.replace(target);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/app/next-seo.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'JPVCDB',
3 | description: 'NextJS Landing Page and Admin Template',
4 | openGraph: {
5 | type: 'website',
6 | locale: 'en_IE',
7 | url: 'https://www.url.ie/',
8 | title: 'Open Graph Title',
9 | description: 'Open Graph Description',
10 | defaultImageWidth: 1200,
11 | defaultImageHeight: 1200,
12 | // Multiple Open Graph images is only available in version `7.0.0-canary.0`+ of next (see note top of README.md)
13 | images: [
14 | {
15 | url: 'https://www.example.ie/og-image-01.jpg',
16 | width: 800,
17 | height: 600,
18 | alt: 'Og Image Alt',
19 | }
20 | ],
21 | site_name: 'SiteName',
22 | },
23 | twitter: {
24 | handle: '@handle',
25 | site: '@site',
26 | cardType: 'summary_large_image',
27 | }
28 | };
29 |
30 |
--------------------------------------------------------------------------------
/src/app/next.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const withLess = require('@zeit/next-less')
3 | const lessToJS = require('less-vars-to-js')
4 | const fs = require('fs')
5 | const path = require('path')
6 |
7 | // Where your antd-custom.less file lives
8 | const themeVariables = lessToJS(
9 | fs.readFileSync(
10 | path.resolve(__dirname, './asserts/antd-custom.less'),
11 | 'utf8'
12 | )
13 | )
14 |
15 | // fix: prevents error when .less files are required by node
16 | if (typeof require !== 'undefined') {
17 | require.extensions['.less'] = (file) => {}
18 | }
19 |
20 | module.exports = withLess({
21 | lessLoaderOptions: {
22 | javascriptEnabled: true,
23 | modifyVars: themeVariables // make your antd custom effective
24 | },
25 | distDir: '../../dist/functions/next'
26 |
27 | })
28 |
29 | // module.exports = {
30 | // distDir: '../../dist/functions/next'
31 | // }
32 |
33 |
--------------------------------------------------------------------------------
/src/app/pages/about.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import About from '../layout/About'
4 | import Head from 'next/head'
5 |
6 | export default () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/src/app/pages/cohort.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import Cohort from '../layout/Cohort'
4 | import Head from 'next/head'
5 |
6 |
7 | const cohort = ({ id }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 |
17 | cohort.getInitialProps = ({ query: { id } }) => {
18 | // console.log("Cohort: ", id)
19 | return { id }
20 | }
21 |
22 | export default cohort
23 |
--------------------------------------------------------------------------------
/src/app/pages/company.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import Company from '../layout/Company'
4 | import Head from 'next/head'
5 |
6 | const company = ({ id }) => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 |
16 | company.getInitialProps = ({ query: { id } }) => {
17 | // console.log("Company: ", id)
18 | return { id }
19 | }
20 |
21 | export default company
22 |
--------------------------------------------------------------------------------
/src/app/pages/dashboard.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import { Menu, Icon, Layout, Breadcrumb, Skeleton} from 'antd';
4 | import styled from 'styled-components';
5 | import { redirectIfNotAuthenticated } from "../lib/auth";
6 | import MFHeader from '../admin/Header';
7 | import MFUsers from '../admin/Users';
8 | import MFMessages from '../admin/Messages';
9 | import MFCompanies from '../admin/Companies';
10 |
11 | const { SubMenu } = Menu;
12 | const { Content, Footer, Sider } = Layout;
13 |
14 | class Dashboard extends React.Component {
15 | state = {
16 | mode: 'inline',
17 | theme: 'light',
18 | collapsed: false,
19 | selectedMenu: '1'
20 | }
21 |
22 | // static async getInitialProps(ctx) {
23 | static getInitialProps(ctx) {
24 | if (redirectIfNotAuthenticated(ctx)) {
25 | return {};
26 | }
27 |
28 | return {};
29 | }
30 |
31 | onCollapse = (collapsed) => {
32 | console.log(collapsed);
33 | this.setState({ collapsed });
34 | }
35 |
36 | headerMenuOnClick = (menuItem) => {
37 | this.setState({
38 | selectedMenu: menuItem.key
39 | });
40 | }
41 |
42 |
43 | render() {
44 | // const panelArray = [ , ]
45 |
46 | return (
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 | Users
59 |
60 |
61 |
62 | Messages
63 |
64 |
65 |
66 | Companies
67 |
68 |
69 |
70 |
71 |
72 |
73 | {this.state.selectedMenu == "1" ?
74 |
75 | : this.state.selectedMenu == "2" ?
76 |
77 | : this.state.selectedMenu == "3" ?
78 |
79 | :
80 |
81 |
82 | Home
83 | Welcome
84 |
85 |
86 |
87 |
88 |
89 | }
90 |
91 |
92 | ©2018-2019 Mobile Flow LLC
93 |
94 |
95 |
96 |
97 |
98 | )
99 | }
100 |
101 | }
102 |
103 | const LogoContainer = styled.div`
104 | height: 80px;
105 | background: url("/static/logo-clear.png") no-repeat center;
106 | background-color: #002140;
107 | margin: 0px;
108 | `
109 |
110 | export default Dashboard
111 |
--------------------------------------------------------------------------------
/src/app/pages/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import Home from '../layout/Home'
4 | import Head from 'next/head'
5 |
6 | export default () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/src/app/pages/login.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import Login from '../layout/Login'
4 | import Head from 'next/head'
5 |
6 | export default () => (
7 |
8 |
9 |
10 |
11 |
12 |
13 | )
14 |
--------------------------------------------------------------------------------
/src/app/pages/ranking.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import App from '../components/App'
3 | import Ranking from '../layout/Ranking'
4 | import Head from 'next/head'
5 |
6 |
7 | const ranking = ({ id }) => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 |
17 | ranking.getInitialProps = ({ query: { id } }) => {
18 | // console.log("Ranking: ", id)
19 | return { id }
20 | }
21 |
22 | export default ranking
23 |
--------------------------------------------------------------------------------
/src/app/scripts/README.md:
--------------------------------------------------------------------------------
1 | # Firebase scripts
2 |
3 | This "scripts" folder contains code for database migrations, seeding and analysis.
4 |
5 |
6 | ## Scripts
7 |
8 |
9 | 1. `add-seed-posts.js` will add seed posts to the database from `/scripts/data/seed-posts.json`.
10 |
11 |
12 | 2. `sample-migration.js` is a simple "migration", a script that will update the documents in the database. In this case, it downloads all posts, makes the title of each post UPPERCASE, and re-saves the post.
13 |
14 |
15 | ## Execution
16 |
17 | Copy Firebase administration credentials into `../credentials/serviceAccountKey.js` then install node packages, `npm install` then run `node add-seed-posts.js` or `node sample-migration.js`
--------------------------------------------------------------------------------
/src/app/scripts/add-airtable-records.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const chalk = require('chalk')
3 | var Airtable = require('airtable');
4 |
5 |
6 | // init firebase
7 | const serviceAccount = require('../credentials/serviceAccountKey.json')
8 | admin.initializeApp({
9 | credential: admin.credential.cert(serviceAccount),
10 | databaseURL: "https://jpvcdb.firebaseio.com"
11 | })
12 | const db = require('firebase-admin').firestore()
13 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
14 |
15 | const airTableAccount = require('../credentials/airTableKey.json')
16 | var base = new Airtable({apiKey: airTableAccount.apiKey}).base(airTableAccount.appKey);
17 |
18 | // add seed posts
19 | console.log(chalk.blue(`Adding seed post data...`))
20 |
21 | base('Table 2').select({
22 | // Selecting the first 3 records in Grid view:
23 | // maxRecords: 3,
24 | pageSize: 10,
25 | view: "Grid view",
26 | cellFormat: 'json'
27 | }).eachPage(function page(records, fetchNextPage) {
28 | // This function (`page`) will get called for each page of records.
29 |
30 | records.forEach(function(record) {
31 | console.log(chalk.cyan('Retrieved', record.get('Name')));
32 | db.collection('airtable').add(record._rawJson.fields)
33 | });
34 |
35 | // To fetch the next page of records, call `fetchNextPage`.
36 | // If there are more records, `page` will get called again.
37 | // If there are no more records, `done` will get called.
38 | fetchNextPage();
39 |
40 | }, function done(err) {
41 | if (err) { console.error(chalk.red(err)); return; }
42 | console.log(chalk.green(`...added seed posts`))
43 | });
44 |
45 |
--------------------------------------------------------------------------------
/src/app/scripts/add-seed-posts.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const chalk = require('chalk')
3 |
4 | const postData = require("./data/seed-posts.json")
5 |
6 | // init firebase
7 | const serviceAccount = require('../credentials/serviceAccountKey.json')
8 | admin.initializeApp({
9 | credential: admin.credential.cert(serviceAccount),
10 | databaseURL: "https://jpvcdb.firebaseio.com"
11 | })
12 | const db = require('firebase-admin').firestore()
13 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
14 |
15 | // add seed posts
16 | console.log(chalk.blue(`Adding seed post data...`))
17 | postData.map( post => db.collection('posts').add(post) )
18 | console.log(chalk.green(`...added seed posts`))
19 |
--------------------------------------------------------------------------------
/src/app/scripts/airtable-migration.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 | const slugify = require('slugify')
5 |
6 | // init firebase
7 | const serviceAccount = require('../credentials/serviceAccountKey.json')
8 | admin.initializeApp({
9 | credential: admin.credential.cert(serviceAccount),
10 | databaseURL: "https://jpvcdb.firebaseio.com"
11 | })
12 | const db = require('firebase-admin').firestore()
13 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
14 |
15 | console.log(chalk.blue(`Migrating airtable data to companies collection...`))
16 |
17 | var cursor = null
18 |
19 | const paginateCompany = start => {
20 | start.get()
21 | // db.collection('airtable').limit(3).get()
22 | .then(snap => {
23 |
24 | cursor = snap
25 |
26 | // Bluebird Promises lets you limit promises running at once:
27 | // http://bluebirdjs.com/docs/api/promise.map.html
28 | return Promise.map(snap.docs, updateCompany, {concurrency: 4})
29 | })
30 | .then( () => {
31 |
32 | if (cursor == null || cursor.docs.length === 0) {
33 | console.log(chalk.green(`✅ done!`))
34 | return
35 | }
36 |
37 | var lastVisible = cursor.docs[cursor.docs.length-1];
38 |
39 | var next = db.collection("airtable").orderBy("Name").startAfter(lastVisible).limit(4);
40 |
41 | paginateCompany(next)
42 |
43 | })
44 | .catch(error => {
45 | console.log(chalk.red(`⚠️ migration error: `), error)
46 | })
47 | }
48 |
49 | const updateCompany = docObj => {
50 |
51 | const doc = docObj.data()
52 |
53 | const id = slugify(doc.Name, {lower: true})
54 |
55 | const createdAt = admin.firestore.Timestamp.now()
56 |
57 | const company = {
58 | id,
59 | name: doc.Name || "",
60 | description: doc.description || doc.Name || "",
61 | www: doc.YCDBUrl || "",
62 | landingpage: doc.landingPage || "",
63 | alexa: doc.alexa || "",
64 | batch: doc.batch || "",
65 | category: doc.category || "",
66 | funding: doc.funding || "",
67 | fundingString: doc.funding || "",
68 | logo: doc.logo || "",
69 | status: doc.status || "",
70 | exit: 0,
71 | twitter: 0,
72 | tweets: 0,
73 | facebook: 0,
74 | employees: 1,
75 | growth: 0,
76 | domains: 0,
77 | links: 0,
78 | linkedin: 0,
79 | ticker: "",
80 | address: "",
81 | hqLocation: "",
82 | coordinates: {
83 | lng: 139.7454,
84 | lat: 35.6586
85 | },
86 | founderCount: 1,
87 | founderBackground: [],
88 |
89 | createdAt
90 | }
91 |
92 | console.log(` migrating company ${company.id}...`)
93 |
94 | return db.collection('companies')
95 | .doc(company.id)
96 | .set(company)
97 | }
98 |
99 |
100 | // Use cursors to paginate over db
101 | // https://firebase.google.com/docs/firestore/query-data/query-cursors
102 |
103 | var first = db.collection("airtable").orderBy("Name").limit(4);
104 |
105 | paginateCompany(first);
106 |
107 | console.log(chalk.blue(` ...iterating`))
108 |
--------------------------------------------------------------------------------
/src/app/scripts/alexa-migration.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 |
5 | // init firebase
6 | const serviceAccount = require('../credentials/serviceAccountKey.json')
7 | admin.initializeApp({
8 | credential: admin.credential.cert(serviceAccount),
9 | databaseURL: "https://jpvcdb.firebaseio.com"
10 | })
11 | const db = require('firebase-admin').firestore()
12 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
13 |
14 | console.log(chalk.blue(`Making all company funding numerical...`))
15 |
16 | // if you're collection is big, you might want to paginate the query
17 | // so you don't download the entire thing at once:
18 | // https://firebase.google.com/docs/firestore/query-data/query-cursors
19 | // TODO - show an example?
20 | db.collection('companies').get()
21 | .then(snap => {
22 | // Bluebird Promises lets you limit promises running at once:
23 | // http://bluebirdjs.com/docs/api/promise.map.html
24 | return Promise.map(snap.docs, updateCompany, {concurrency: 5})
25 | })
26 | .then( () => {
27 | console.log(chalk.green(`✅ done!`))
28 | })
29 | .catch(error => {
30 | console.log(chalk.red(`⚠️ migration error: `), error)
31 | })
32 |
33 | const updateCompany = doc => {
34 | console.log(` migrating company ${doc.id}...`)
35 |
36 | let old_alexa = doc.data().alexa
37 | var alexa = 0
38 |
39 | if (typeof old_alexa === 'string' ) {
40 | let alexaString = doc.data().alexa || "0"
41 | let alexaClean = alexaString.replace(/\$|,|m/gi, '')
42 | let alexaInt = parseInt(alexaClean)
43 | alexa = Number(alexaInt)
44 | }
45 | else {
46 | alexa = old_alexa
47 | }
48 |
49 |
50 | // console.log(` ${old_alexa} -> ${alexa}`)
51 |
52 | return db.collection('companies')
53 | .doc(doc.id)
54 | .update({
55 | alexa
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/scripts/algolia-migration.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 | const Algolia = require('algoliasearch')
5 |
6 |
7 | // init firebase
8 | const serviceAccount = require('../credentials/serviceAccountKey.json')
9 | admin.initializeApp({
10 | credential: admin.credential.cert(serviceAccount),
11 | databaseURL: "https://jpvcdb.firebaseio.com"
12 | })
13 | const db = require('firebase-admin').firestore()
14 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
15 |
16 | // Init Algolia
17 | const algoliaAccount = require('../credentials/algoliaAccountKey.json')
18 | let algolia = Algolia(algoliaAccount.app_id, algoliaAccount.admin_key)
19 | let companiesIndex = algolia.initIndex('companies')
20 |
21 | console.log(chalk.blue(`Uploading firebase companies data to algolia search...`))
22 |
23 | var cursor = null
24 |
25 | const paginateCompany = start => {
26 | start.get()
27 | // db.collection('airtable').limit(3).get()
28 | .then(snap => {
29 |
30 | cursor = snap
31 |
32 | // Bluebird Promises lets you limit promises running at once:
33 | // http://bluebirdjs.com/docs/api/promise.map.html
34 | return Promise.map(snap.docs, updateCompany, {concurrency: 4})
35 | })
36 | .then( () => {
37 |
38 | if (cursor == null || cursor.docs.length === 0) {
39 | console.log(chalk.green(`✅ done!`))
40 | return
41 | }
42 |
43 | var lastVisible = cursor.docs[cursor.docs.length-1];
44 |
45 | var next = db.collection("companies").orderBy("name").startAfter(lastVisible).limit(4);
46 |
47 | paginateCompany(next)
48 |
49 | })
50 | .catch(error => {
51 | console.log(chalk.red(`⚠️ migration error: `), error)
52 | })
53 | }
54 |
55 | const updateCompany = docObj => {
56 |
57 | const doc = docObj.data()
58 |
59 |
60 | const algolia = {
61 | objectID: doc.id,
62 | name: doc.name,
63 | description: doc.description,
64 | alexa: doc.alexa,
65 | funding: doc.funding,
66 | logo: doc.logo,
67 | status: doc.status,
68 | exit: doc.exit,
69 | ticker: doc.ticker,
70 | address: doc.address
71 | }
72 |
73 | console.log(` migrating company ${doc.id}...`)
74 |
75 | return companiesIndex.saveObject(algolia)
76 |
77 | }
78 |
79 |
80 | // Use cursors to paginate over db
81 | // https://firebase.google.com/docs/firestore/query-data/query-cursors
82 |
83 | var first = db.collection("companies").orderBy("name").limit(4);
84 |
85 | paginateCompany(first);
86 |
87 | console.log(chalk.blue(` ...iterating`))
88 |
--------------------------------------------------------------------------------
/src/app/scripts/data/seed-companies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "10-by-10",
4 | "name": "10 By 10",
5 | "batch": "S17",
6 | "description": "10 By 10",
7 | "category": "Other SaaS",
8 | "employees": 1,
9 | "hqLocation": "",
10 | "founderBackground": [
11 |
12 | ],
13 | "links": 0,
14 | "exit": 0,
15 | "twitter": 0,
16 | "alexa": 1988722,
17 | "growth": 0,
18 | "founderCount": 1,
19 | "facebook": 0,
20 | "domains": 0,
21 | "logo": "",
22 | "fundingString": "$0.1m",
23 | "www": "http://www.10by10.io",
24 | "address": "",
25 | "funding": 0.1,
26 | "status": "Live",
27 | "linkedin": 0,
28 | "ticker": "",
29 | "landingpage": "",
30 | "tweets": 0,
31 | "createdAt": {
32 | "_seconds": 1544759392,
33 | "_nanoseconds": 412000000
34 | }
35 | },
36 | {
37 | "id": "1000memories",
38 | "name": "1000Memories",
39 | "batch": "S10",
40 | "description": "1000Memories",
41 | "category": "Consumer",
42 | "employees": 1,
43 | "www": "http://www.ancestry.com",
44 | "address": "",
45 | "funding": 333.2,
46 | "status": "Exited",
47 | "linkedin": 0,
48 | "ticker": "",
49 | "landingpage": "",
50 | "tweets": 0,
51 | "hqLocation": "",
52 | "coordinates": {
53 | "lng": 139.7454,
54 | "lat": 35.6586
55 | },
56 | "founderBackground": [
57 |
58 | ],
59 | "links": 0,
60 | "exit": 0,
61 | "twitter": 0,
62 | "alexa": 1082,
63 | "growth": 0,
64 | "founderCount": 1,
65 | "facebook": 0,
66 | "domains": 0,
67 | "logo": "1,342",
68 | "fundingString": "$333.2m",
69 | "performance": {
70 | "funding":5.3,"fundingCat":100,
71 | "employees":11.2,"employeesCat":33.0,
72 | "growth":12.1,"growthCat":82.44,
73 | "alexa":52.23,"alexaCat":52.22,
74 | "twitter":2.0,"twitterCat":80
75 | },
76 | "createdAt": {
77 | "_seconds": 1544759392,
78 | "_nanoseconds": 418000000
79 | }
80 | },
81 | {
82 | "id": "airbnb",
83 | "name": "Airbnb",
84 | "batch": "W09",
85 | "description": "Airbnb",
86 | "category": "Consumer",
87 | "exit": 0,
88 | "twitter": 0,
89 | "alexa": 343,
90 | "growth": 0,
91 | "founderCount": 1,
92 | "facebook": 0,
93 | "domains": 0,
94 | "logo": "6,100",
95 | "fundingString": "$6,000.3m",
96 | "performance": {
97 | "funding":5.3,"fundingCat":100,
98 | "employees":11.2,"employeesCat":33.0,
99 | "growth":12.1,"growthCat":82.44,
100 | "alexa":52.23,"alexaCat":52.22,
101 | "twitter":2.0,"twitterCat":80
102 | },
103 | "www": "http://www.airbnb.com",
104 | "address": "",
105 | "funding": 6000.3,
106 | "status": "Live",
107 | "linkedin": 0,
108 | "ticker": "",
109 | "landingpage": "",
110 | "tweets": 0,
111 | "employees": 1,
112 | "hqLocation": "",
113 | "coordinates": {
114 | "lng": 139.7454,
115 | "lat": 35.6586
116 | },
117 | "founderBackground": [
118 |
119 | ],
120 | "links": 0,
121 | "createdAt": {
122 | "_seconds": 1544759396,
123 | "_nanoseconds": 228000000
124 | }
125 | }
126 | ]
--------------------------------------------------------------------------------
/src/app/scripts/data/seed-posts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "Created from a script",
4 | "content": "This post was created by running a test script locally",
5 | "slug": "created-from-a-script"
6 | },
7 | {
8 | "title": "Another interloper",
9 | "content": "This post was ALSO created by running a test script locally",
10 | "slug": "another-interloper"
11 | }
12 | ]
13 |
--------------------------------------------------------------------------------
/src/app/scripts/generate-analytics.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 |
5 | // init firebase
6 | const serviceAccount = require('../credentials/serviceAccountKey.json')
7 | admin.initializeApp({
8 | credential: admin.credential.cert(serviceAccount),
9 | databaseURL: "https://jpvcdb.firebaseio.com"
10 | })
11 | const db = require('firebase-admin').firestore()
12 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
13 |
14 |
15 | /* Analyze company data to generate a static JSON array cohort data including:
16 | - cohort investment count
17 | - cohort status: # exited, live or dead
18 | - cohort funding tranch: # mega, mini, seed and no round
19 | */
20 |
21 | console.log(chalk.blue(`Generating analytics data...`))
22 |
23 | db.collection('companies').get()
24 | .then(snap => {
25 | return generateAnalyticsJSON( snap.docs )
26 | })
27 | .then( () => {
28 | console.log(chalk.green(`✅ done!`))
29 | })
30 | .catch(error => {
31 | console.log(chalk.red(`⚠️ error: `), error)
32 | })
33 |
34 |
35 | const generateAnalyticsJSON = docs => {
36 | console.log(` analyzing company data ${docs.length}...`)
37 |
38 | // Testing...
39 | // let data = [{"id":"10-by-10","address":"","alexa":1988722,"batch":"S17","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":412000000},"description":"10 By 10","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.1,"fundingString":"$0.1m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"10 By 10","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.10by10.io"},{"id":"1000memories","address":"","alexa":1082,"batch":"S10","category":"Consumer","createdAt":{"seconds":1544759392,"nanoseconds":418000000},"description":"1000Memories","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":333.2,"fundingString":"$333.2m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"1,342","name":"1000Memories","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.ancestry.com"},{"id":"280-north","address":"","alexa":0,"batch":"W08","category":"Dev Tools","createdAt":{"seconds":1544759392,"nanoseconds":419000000},"description":"280 North","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.3,"fundingString":"$0.3m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"280 North","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.280north.com"},{"id":"42","address":"","alexa":2715414,"batch":"W14","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":420000000},"description":"42","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0,"fundingString":"$0.0m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"10","name":"42","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.42technologies.com"},{"id":"42floors","address":"","alexa":151193,"batch":"W12","category":"Real Estate","createdAt":{"seconds":1544759392,"nanoseconds":954000000},"description":"42Floors","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":17.4,"fundingString":"$17.4m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"40","name":"42Floors","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.42floors.com"},{"id":"500friends","address":"","alexa":761677,"batch":"W10","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":955000000},"description":"500Friends","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":12.9,"fundingString":"$12.9m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"6,800","name":"500Friends","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.500friends.com"},{"id":"70millionjobs","address":"","alexa":1100618,"batch":"S17","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":956000000},"description":"70MillionJobs","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":1.6,"fundingString":"$1.6m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"70MillionJobs","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.70millionjobs.com"},{"id":"7cups","address":"","alexa":44582,"batch":"S13","category":"Healthcare","createdAt":{"seconds":1544759392,"nanoseconds":957000000},"description":"7cups","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0,"fundingString":"$0.0m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"190","name":"7cups","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.7cups.com"},{"id":"80000-hours","address":"","alexa":64762,"batch":"S15","category":"Nonprofit","createdAt":{"seconds":1544759393,"nanoseconds":474000000},"description":"80,000 Hours","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.1,"fundingString":"$0.1m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"15","name":"80,000 Hours","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.80000hours.org"},{"id":"9gag","address":"","alexa":377,"batch":"S12","category":"Entertainment","createdAt":{"seconds":1544759393,"nanoseconds":475000000},"description":"9gag","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":2.9,"fundingString":"$2.9m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"140","name":"9gag","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.9gag.com"}]
40 | // let batches = data.map( co => co.batch )
41 |
42 | let data = docs.map( doc => doc.data() )
43 |
44 | let batches = data.map( co => co.batch )
45 |
46 | let sortedBatches = [ ...new Set(batches)].sort((a,b) => parseInt(a.replace(/[ws]/i,'')) - parseInt(b.replace(/[ws]/i,'')))
47 |
48 | const totalFunding = (d, b) => d.filter( co => co.batch == b).reduce( (funding,co) => funding + co.funding, 0)
49 |
50 | var sortedFunding = sortedBatches.map( batch => totalFunding(data, batch) )
51 |
52 | const totalCompanies = (d, b) => d.filter( co => co.batch == b).length
53 |
54 | var sortedCount = sortedBatches.map( batch => totalCompanies(data, batch) )
55 |
56 | const totalStatus = (d,b,s) => d.filter( co => co.batch == b && co.status == s).length
57 |
58 | var sortedLive = sortedBatches.map( batch => totalStatus(data, batch, 'Live') )
59 | var sortedExited = sortedBatches.map( batch => totalStatus(data, batch, 'Exited') )
60 | var sortedDead = sortedBatches.map( batch => totalStatus(data, batch, 'Dead') )
61 |
62 | const totalLevel = (d,b,min,max) => d.filter( co => co.batch == b && co.funding > min && co.funding <= max).length
63 |
64 | var sortedMega = sortedBatches.map( batch => totalLevel(data, batch, 10.0, 99999999.0) )
65 | var sortedMini = sortedBatches.map( batch => totalLevel(data, batch, 5.0, 10.0) )
66 | var sortedSeed = sortedBatches.map( batch => totalLevel(data, batch, 0.0, 5.0) )
67 | var sortedNone = sortedBatches.map( batch => totalLevel(data, batch, -1.0, 0.0) )
68 |
69 | // Calcluate batch data
70 |
71 | var batchData = []
72 |
73 | let maxBatchCount = Math.max(...sortedCount) // batch match
74 |
75 | for (var [i,b] of sortedBatches.entries()) {
76 | // console.log(`i: ${i} = ${b}, ${sortedCount[i]}`)
77 | let percent = ( maxBatchCount != 0 ? sortedCount[i] / maxBatchCount : 1.0 ) * 100.0
78 |
79 | let live = sortedLive[i]
80 | let dead = sortedDead[i]
81 | let exited = sortedExited[i]
82 |
83 | let mega = sortedMega[i]
84 | let mini = sortedMini[i]
85 | let seed = sortedSeed[i]
86 | let none = sortedNone[i]
87 |
88 | var dataObj = {batch: b, count: sortedCount[i], percent, exited, live, dead, mega, mini, seed, none}
89 | batchData.push(dataObj)
90 | }
91 |
92 | console.log(JSON.stringify(batchData))
93 |
94 | return batchData
95 | }
--------------------------------------------------------------------------------
/src/app/scripts/generate-ranking.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 |
5 | // init firebase
6 | const serviceAccount = require('../credentials/serviceAccountKey.json')
7 | admin.initializeApp({
8 | credential: admin.credential.cert(serviceAccount),
9 | databaseURL: "https://jpvcdb.firebaseio.com"
10 | })
11 | const db = require('firebase-admin').firestore()
12 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
13 |
14 |
15 | /* Analyze company data to generate a static JSON array ranking data including:
16 | - company ranking of funding vs. all companies & peers
17 | */
18 |
19 | console.log(chalk.blue(`Generating company ranking data...`))
20 |
21 | /*
22 | db.collection('companies').get()
23 | .then(snap => {
24 | return generateAnalyticsJSON( snap.docs )
25 | })
26 | .then( () => {
27 | console.log(chalk.green(`✅ done!`))
28 | })
29 | .catch(error => {
30 | console.log(chalk.red(`⚠️ error: `), error)
31 | })
32 | */
33 |
34 | const generateAnalyticsJSON = docs => {
35 | console.log(` analyzing company data ${docs.length}...`)
36 |
37 | // Testing...
38 | let data = [{"id":"10-by-10","address":"","alexa":1988722,"batch":"S17","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":412000000},"description":"10 By 10","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.1,"fundingString":"$0.1m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"10 By 10","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.10by10.io"},{"id":"1000memories","address":"","alexa":1082,"batch":"S10","category":"Consumer","createdAt":{"seconds":1544759392,"nanoseconds":418000000},"description":"1000Memories","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":333.2,"fundingString":"$333.2m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"1,342","name":"1000Memories","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.ancestry.com"},{"id":"280-north","address":"","alexa":0,"batch":"W08","category":"Dev Tools","createdAt":{"seconds":1544759392,"nanoseconds":419000000},"description":"280 North","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.3,"fundingString":"$0.3m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"280 North","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.280north.com"},{"id":"42","address":"","alexa":2715414,"batch":"W14","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":420000000},"description":"42","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0,"fundingString":"$0.0m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"10","name":"42","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.42technologies.com"},{"id":"42floors","address":"","alexa":151193,"batch":"W12","category":"Real Estate","createdAt":{"seconds":1544759392,"nanoseconds":954000000},"description":"42Floors","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":17.4,"fundingString":"$17.4m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"40","name":"42Floors","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.42floors.com"},{"id":"500friends","address":"","alexa":761677,"batch":"W10","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":955000000},"description":"500Friends","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":12.9,"fundingString":"$12.9m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"6,800","name":"500Friends","status":"Exited","ticker":"","tweets":0,"twitter":0,"www":"http://www.500friends.com"},{"id":"70millionjobs","address":"","alexa":1100618,"batch":"S17","category":"Other SaaS","createdAt":{"seconds":1544759392,"nanoseconds":956000000},"description":"70MillionJobs","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":1.6,"fundingString":"$1.6m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"","name":"70MillionJobs","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.70millionjobs.com"},{"id":"7cups","address":"","alexa":44582,"batch":"S13","category":"Healthcare","createdAt":{"seconds":1544759392,"nanoseconds":957000000},"description":"7cups","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0,"fundingString":"$0.0m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"190","name":"7cups","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.7cups.com"},{"id":"80000-hours","address":"","alexa":64762,"batch":"S15","category":"Nonprofit","createdAt":{"seconds":1544759393,"nanoseconds":474000000},"description":"80,000 Hours","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":0.1,"fundingString":"$0.1m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"15","name":"80,000 Hours","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.80000hours.org"},{"id":"9gag","address":"","alexa":377,"batch":"S12","category":"Entertainment","createdAt":{"seconds":1544759393,"nanoseconds":475000000},"description":"9gag","domains":0,"employees":1,"exit":0,"facebook":0,"founderBackground":[],"founderCount":1,"funding":2.9,"fundingString":"$2.9m","growth":0,"hqLocation":"","landingpage":"","linkedin":0,"links":0,"logo":"140","name":"9gag","status":"Live","ticker":"","tweets":0,"twitter":0,"www":"http://www.9gag.com"}]
39 |
40 | // let data = docs.map( doc => doc.data() )
41 |
42 | const maxFunding = (d) => d.reduce( (funding,co) => co.funding > funding ? co.funding : funding, 0)
43 | const maxFundingCat = (d, c) => d.filter( co => co.category == c).reduce( (funding,co) => co.funding > funding ? co.funding : funding, 0)
44 |
45 | const minAlexa = (d) => d.reduce( (alexa,co) => co.alexa < alexa ? co.alexa : alexa, 0)
46 | const minAlexaCat = (d, c) => d.filter( co => co.category == c).reduce( (alexa,co) => co.alexa < alexa ? co.alexa : alexa, 0)
47 |
48 | const maxTwitter = (d) => d.reduce( (twitter,co) => co.twitter > twitter ? co.twitter : twitter, 0)
49 | const maxTwitterCat = (d, c) => d.filter( co => co.category == c).reduce( (twitter,co) => co.twitter > twitter ? co.twitter : twitter, 0)
50 |
51 | const maxEmployee = (d) => d.reduce( (employees,co) => co.employees > employees ? co.employees : employees, 0)
52 | const maxEmployeeCat = (d, c) => d.filter( co => co.category == c).reduce( (employees,co) => co.employees > employees ? co.employees : employees, 0)
53 |
54 | const maxGrowth = (d) => d.reduce( (growth,co) => co.growth > growth ? co.growth : growth, 0)
55 | const maxGrowthCat = (d, c) => d.filter( co => co.category == c).reduce( (growth,co) => co.growth > growth ? co.growth : growth, 0)
56 |
57 | // Calcluate company data
58 |
59 | var companyData = []
60 |
61 | let max_funding = maxFunding(data)
62 | let min_alexa = minAlexa(data)
63 | let max_twitter = maxTwitter(data)
64 | let max_employee = maxEmployee(data)
65 | let max_growth = maxGrowth(data)
66 |
67 | for (var [i,co] of data.entries()) {
68 | console.log(`i: ${i} = ${co.name}`)
69 |
70 | let id = co.id
71 | let category = co.category
72 |
73 | let max_funding_cat = maxFundingCat(data,category)
74 | let min_alexa_cat = minAlexaCat(data,category)
75 | let max_twitter_cat = maxTwitterCat(data,category)
76 | let max_employee_cat = maxEmployeeCat(data,category)
77 | let max_growth_cat = maxGrowthCat(data,category)
78 |
79 | let funding = ( max_funding != 0 ? co.funding / max_funding : 1.0 ) * 100.0
80 | let fundingCat = ( max_funding_cat != 0 ? co.funding / max_funding_cat : 1.0 ) * 100.0
81 |
82 | let alexa = ( min_alexa != 0 ? co.alexa / min_alexa : 1.0 ) * 100.0
83 | let alexaCat = ( min_alexa_cat != 0 ? co.alexa / min_alexa_cat : 1.0 ) * 100.0
84 |
85 | let twitter = ( max_twitter != 0 ? co.twitter / max_twitter : 1.0 ) * 100.0
86 | let twitterCat = ( max_twitter_cat != 0 ? co.twitter / max_twitter_cat : 1.0 ) * 100.0
87 |
88 | let employees = ( max_employee != 0 ? co.employees / max_employee : 1.0 ) * 100.0
89 | let employeesCat = ( max_employee_cat != 0 ? co.employees / max_employee_cat : 1.0 ) * 100.0
90 |
91 | let growth = ( max_growth != 0 ? co.growth / max_growth : 1.0 ) * 100.0
92 | let growthCat = ( max_growth_cat != 0 ? co.growth / max_growth_cat : 1.0 ) * 100.0
93 |
94 | var dataObj = {id, category, performance: {funding, fundingCat, alexa, alexaCat, twitter, twitterCat, employees, employeesCat, growth, growthCat}}
95 | companyData.push(dataObj)
96 | }
97 |
98 | console.log(JSON.stringify(companyData))
99 |
100 | return companyData
101 | }
102 |
103 | // Tesing
104 | generateAnalyticsJSON(["test"])
--------------------------------------------------------------------------------
/src/app/scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebased-scripts",
3 | "version": "1.0.0",
4 | "description": "maint scripts to help developers work with data in firebase apps",
5 | "main": "index.js",
6 | "dependencies": {
7 | "bluebird": "^3.5.3",
8 | "chalk": "^2.4.1",
9 | "airtable": "^0.5.8",
10 | "firebase-admin": "^6.3.0",
11 | "slugify": "^1.3.4",
12 | "algoliasearch": "^3.32.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/scripts/sample-migration.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin')
2 | const Promise = require('bluebird')
3 | const chalk = require('chalk')
4 |
5 | // init firebase
6 | const serviceAccount = require('../credentials/serviceAccountKey.json')
7 | admin.initializeApp({
8 | credential: admin.credential.cert(serviceAccount),
9 | databaseURL: "https://jpvcdb.firebaseio.com"
10 | })
11 | const db = require('firebase-admin').firestore()
12 | db.settings({timestampsInSnapshots: true}) // Using Timestamps
13 |
14 | console.log(chalk.blue(`Making all company funding numerical...`))
15 |
16 | // if you're collection is big, you might want to paginate the query
17 | // so you don't download the entire thing at once:
18 | // https://firebase.google.com/docs/firestore/query-data/query-cursors
19 | // TODO - show an example?
20 | db.collection('companies').get()
21 | .then(snap => {
22 | // Bluebird Promises lets you limit promises running at once:
23 | // http://bluebirdjs.com/docs/api/promise.map.html
24 | return Promise.map(snap.docs, updateCompany, {concurrency: 5})
25 | })
26 | .then( () => {
27 | console.log(chalk.green(`✅ done!`))
28 | })
29 | .catch(error => {
30 | console.log(chalk.red(`⚠️ migration error: `), error)
31 | })
32 |
33 | const updateCompany = doc => {
34 | console.log(` migrating company ${doc.id}...`)
35 |
36 | let fundingString = doc.data().fundingString || "0.0"
37 | let fundingClean = fundingString.replace(/\$|,|m/gi, '')
38 | let fundingFloat = parseFloat(fundingClean)
39 | let funding = Number(fundingFloat)
40 |
41 |
42 | // console.log(` ${fundingString} -> ${funding}}`)
43 |
44 | return db.collection('companies')
45 | .doc(doc.id)
46 | .update({
47 | funding
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/static/header-hero.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/header-hero.jpg
--------------------------------------------------------------------------------
/src/app/static/header-hero2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/header-hero2.jpg
--------------------------------------------------------------------------------
/src/app/static/header-hero3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/header-hero3.jpg
--------------------------------------------------------------------------------
/src/app/static/laptop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/app/static/logo-clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/logo-clear.png
--------------------------------------------------------------------------------
/src/app/static/logo-clear@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/logo-clear@2x.png
--------------------------------------------------------------------------------
/src/app/static/logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/logo-white.png
--------------------------------------------------------------------------------
/src/app/static/logo-white@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/logo-white@2x.png
--------------------------------------------------------------------------------
/src/app/static/logo-word-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/app/static/logo-word-white.png
--------------------------------------------------------------------------------
/src/functions/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "6.11.5"
8 | }
9 | }
10 | ]
11 | ]
12 | }
--------------------------------------------------------------------------------
/src/functions/index.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions')
2 | const { parse } = require('url')
3 | const next = require('next')
4 | const pathMatch = require('path-match')
5 |
6 | var dev = process.env.NODE_ENV !== 'production'
7 | var app = next({ dev, conf: { distDir: 'next' } })
8 | var handle = app.getRequestHandler()
9 | const route = pathMatch()
10 | const matchCompany = route('/company/:id')
11 | const matchCohort = route('/cohort/:id')
12 | const matchRanking = route('/ranking/:id')
13 |
14 |
15 |
16 | exports.next = functions.https.onRequest((req, res) => {
17 | console.log('File: ' + req.originalUrl) // log the page.js file that is being requested
18 | // return app.prepare().then(() => handle(req, res))
19 | return app.prepare()
20 | .then(() => {
21 | const { pathname, query } = parse(req.url, true)
22 |
23 | const paramsCompany = matchCompany(pathname)
24 | if (paramsCompany !== false) {
25 | // assigning `query` into the params means that we still
26 | // get the query string passed to our application
27 | // i.e. /blog/foo?show-comments=true
28 | app.render(req, res, '/company', Object.assign(paramsCompany, query))
29 | return
30 | }
31 |
32 | const paramsCohort = matchCohort(pathname)
33 | if (paramsCohort !== false) {
34 | app.render(req, res, '/cohort', Object.assign(paramsCohort, query))
35 | return
36 | }
37 |
38 | const paramsRanking = matchRanking(pathname)
39 | if (paramsRanking !== false) {
40 | app.render(req, res, '/ranking', Object.assign(paramsRanking, query))
41 | return
42 | }
43 |
44 | handle(req, res)
45 | })
46 |
47 | })
48 |
--------------------------------------------------------------------------------
/src/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/public/favicon.ico
--------------------------------------------------------------------------------
/src/public/placeholder.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moflo/jpvcdb/fe87bd2d73f79d72bc600c695b5575e7600ead41/src/public/placeholder.html
--------------------------------------------------------------------------------