├── .eslintrc
├── .env.test
├── .storybook
├── addons.js
├── config.js
└── presets.js
├── .env.development
├── public
├── favicon.ico
├── assets
│ ├── favicon.ico
│ ├── og-image.jpg
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── mstile-150x150.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── browserconfig.xml
│ ├── manifest.json
│ └── safari-pinned-tab.svg
├── robots.txt
└── index.html
├── .env.production
├── src
├── components
│ ├── Footer
│ │ ├── index.stories.js
│ │ ├── index.test.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ ├── Background
│ │ ├── index.test.js
│ │ ├── __snapshots__
│ │ │ └── index.test.js.snap
│ │ ├── index.stories.js
│ │ └── index.js
│ ├── Statistics
│ │ ├── index.test.js
│ │ ├── index.stories.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ ├── RecommendationsList
│ │ ├── index.stories.js
│ │ ├── index.test.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ ├── SubscribeMessage
│ │ ├── index.stories.js
│ │ ├── index.test.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ └── Recommendations
│ │ ├── index.stories.js
│ │ └── index.js
├── setupTests.js
├── utils
│ ├── index.js
│ └── fixtures.js
├── containers
│ ├── HomePage
│ │ ├── index.stories.js
│ │ ├── index.test.js
│ │ ├── index.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ ├── SubscribeMessage
│ │ ├── index.js
│ │ ├── index.test.js
│ │ └── __snapshots__
│ │ │ └── index.test.js.snap
│ └── RecommendationsPage.js
├── constants
│ └── index.js
├── App.js
├── types
│ └── index.js
└── index.js
├── .editorconfig
├── README.md
├── .env
├── .gitignore
├── .flowconfig
├── .circleci
└── config.yml
├── LICENSE.md
└── package.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app"
3 | }
4 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | REACT_APP_API_HOST=https://ghrecommender.local
2 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register'
2 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_API_HOST=http://api.ghrecommender.localhost:8000
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/favicon.ico
--------------------------------------------------------------------------------
/public/assets/og-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/og-image.jpg
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Disallow:
4 | Disallow: /api/
5 | Disallow: /admin/
6 | Disallow: /login/
7 |
--------------------------------------------------------------------------------
/public/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/public/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/public/assets/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/mstile-150x150.png
--------------------------------------------------------------------------------
/public/assets/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/apple-touch-icon.png
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_API_HOST=https://ghrecommender.io
2 | REACT_APP_RAVEN_DSN=https://a8004e87dcc34ef98d817e31761aba7d@sentry.io/218105
3 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react'
2 |
3 | configure(require.context('../src', true, /\.stories\.js$/), module)
4 |
--------------------------------------------------------------------------------
/public/assets/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/assets/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ghrecommender/ghrecommender-frontend/HEAD/public/assets/android-chrome-512x512.png
--------------------------------------------------------------------------------
/.storybook/presets.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | name: '@storybook/addon-docs/react/preset',
4 | options: {
5 | configureJSX: true,
6 | },
7 | },
8 | ]
9 |
--------------------------------------------------------------------------------
/src/components/Footer/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Footer from './index'
4 |
5 | export default { title: 'Components|Footer' }
6 |
7 | export const footer = () =>
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 |
8 | [*.{js,json}]
9 | charset = utf-8
10 | indent_style = space
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme'
2 | import Adapter from 'enzyme-adapter-react-16'
3 |
4 | configure({ adapter: new Adapter() })
5 |
6 | global.requestAnimationFrame = function(callback) {
7 | setTimeout(callback, 0)
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import ReactGA from 'react-ga'
4 |
5 | export const trackClick = (label: string) => () => {
6 | ReactGA.event({
7 | category: 'User',
8 | action: `Clicked ${label}`,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/Footer/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 |
4 | import Footer from './index'
5 |
6 | test('', () => {
7 | const component = renderer.create()
8 | expect(component.toJSON()).toMatchSnapshot()
9 | })
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GHRecommender
2 |
3 | [](https://circleci.com/gh/ghrecommender/ghrecommender-frontend)
4 |
5 | GHRecommender - personalized recommendations for GitHub projects based on information about repositories starred by the user.
6 |
--------------------------------------------------------------------------------
/src/components/Background/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import rendered from 'react-test-renderer'
3 |
4 | import Background from './index'
5 |
6 |
7 | test(' ', () => {
8 | const component = rendered.create( )
9 | expect(component.toJSON()).toMatchSnapshot()
10 | })
11 |
--------------------------------------------------------------------------------
/public/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #00aba9
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/Statistics/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 |
4 | import Statistics from './index'
5 |
6 |
7 |
8 | test(' ', () => {
9 | const component = renderer.create( )
10 | expect(component.toJSON()).toMatchSnapshot()
11 | })
12 |
--------------------------------------------------------------------------------
/src/containers/HomePage/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MemoryRouter } from 'react-router-dom'
3 |
4 | import HomePage from './index'
5 |
6 | export default {
7 | title: 'Pages|Home',
8 | decorators: [story => {story()} ],
9 | }
10 |
11 | export const homePage = () =>
12 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_VERSION=0.0.1
2 |
3 | REACT_APP_TITLE="GHRecommender - get personalized recommendations for GitHub projects"
4 | REACT_APP_DESCRIPTION="GHRecommender - personalized recommendations for GitHub projects based on information about repositories starred by the user"
5 | REACT_APP_IMAGE=https://ghrecommender.io/assets/og-image.jpg
6 |
7 | REACT_APP_GA=UA-106627857-1
8 |
--------------------------------------------------------------------------------
/src/components/Background/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
5 |
16 |
17 | `;
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 | /flow-typed
3 |
4 | # dependencies
5 | /node_modules
6 |
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /build
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
--------------------------------------------------------------------------------
/src/containers/HomePage/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 | import { MemoryRouter } from 'react-router-dom'
4 |
5 | import HomePage from './index'
6 |
7 | test(' ', () => {
8 | const component = renderer.create(
9 |
10 |
11 | ,
12 | )
13 | expect(component.toJSON()).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/components/RecommendationsList/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'semantic-ui-css/semantic.min.css'
3 |
4 | import RecommendationsList from './index'
5 | import { recommendations } from '../../utils/fixtures'
6 |
7 | export default {
8 | title: 'Components|List',
9 | component: RecommendationsList,
10 | }
11 |
12 | export const recommendationsList = () =>
13 |
--------------------------------------------------------------------------------
/src/components/Background/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Background from './index'
4 |
5 | const style = { backgroundColor: '#1b1c1d', width: '100%', height: '100%', position: 'absolute' }
6 |
7 | export default {
8 | title: 'Components|Background',
9 | decorators: [story => {story()}
],
10 | component: Background,
11 | }
12 |
13 | export const background = () =>
14 |
--------------------------------------------------------------------------------
/src/components/Statistics/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import 'semantic-ui-css/semantic.min.css'
3 |
4 | import Statistics from './index'
5 |
6 | const style = { backgroundColor: '#1b1c1d', width: '100%', height: '100%', position: 'absolute' }
7 |
8 | export default {
9 | title: 'Components|Statistics',
10 | decorators: [story => {story()}
],
11 | }
12 |
13 | export const statistics = () =>
14 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | /node_modules/http-parser-js/.*
3 | /node_modules/immutable/.*
4 | /node_modules/@storybook/.*
5 | /node_modules/radium/.*
6 |
7 | [include]
8 |
9 | [libs]
10 |
11 | [lints]
12 | sketchy-null=warn
13 |
14 | [options]
15 | emoji=true
16 |
17 | [strict]
18 | ; nonstrict-import
19 | unclear-type
20 | unsafe-getters-setters
21 | untyped-import
22 | untyped-type-import
23 | sketchy-null
24 |
--------------------------------------------------------------------------------
/src/components/Statistics/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 | import { Statistic } from 'semantic-ui-react'
5 |
6 | const items = [
7 | { label: 'Users', value: '19,925,204', key: 'users' },
8 | { label: 'Repositories', value: '64,245,061', key: 'repositories' },
9 | { label: 'Stars', value: '81,302,007', key: 'stars' },
10 | ]
11 |
12 | const StatisticGroups = () => (
13 |
14 | )
15 |
16 | export default StatisticGroups
17 |
--------------------------------------------------------------------------------
/public/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GHRecommender",
3 | "icons": [
4 | {
5 | "src": "/assets/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | },
9 | {
10 | "src": "/assets/android-chrome-512x512.png",
11 | "sizes": "512x512",
12 | "type": "image/png"
13 | }
14 | ],
15 | "theme_color": "#ffffff",
16 | "background_color": "#ffffff",
17 | "display": "standalone"
18 | }
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import { isNil } from 'ramda'
4 |
5 | const { REACT_APP_API_HOST } = process.env
6 |
7 | if (isNil(REACT_APP_API_HOST)) throw new Error('REACT_APP_API_HOST not found')
8 |
9 | export const API_HOST = REACT_APP_API_HOST
10 | export const API_USER_URL = `${API_HOST}/api/user/`
11 | export const API_RECOMMENDATIONS_URL = `${API_HOST}/api/recommendations/`
12 | export const API_SUBSCRIBE_URL = `${API_HOST}/api/subscription/`
13 | export const LOGIN_URL = `${API_HOST}/login/github/`
14 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import { BrowserRouter as Router, Route } from 'react-router-dom'
6 |
7 | import HomePage from './containers/HomePage'
8 | import RecommendationsPage from './containers/RecommendationsPage'
9 |
10 | import 'semantic-ui-css/semantic.min.css'
11 |
12 | const App = () => (
13 |
14 | <>
15 |
16 |
17 | >
18 |
19 | )
20 |
21 | export default App
22 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/node:10.15
6 | working_directory: ~/repo
7 |
8 | steps:
9 | - checkout
10 |
11 | - restore_cache:
12 | name: Restore Yarn Package Cache
13 | keys:
14 | - yarn-packages-{{ checksum "yarn.lock" }}
15 |
16 | - run:
17 | name: Install Dependencies
18 | command: yarn install --frozen-lockfile
19 |
20 | - save_cache:
21 | name: Save Yarn Package Cache
22 | paths:
23 | - ~/.cache/yarn
24 | key: yarn-packages-{{ checksum "yarn.lock" }}
25 |
26 | # run tests!
27 | - run: CI=true yarn test
28 | - run: yarn lint
29 | - run: yarn flow --max-workers=1
30 |
--------------------------------------------------------------------------------
/src/types/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | export type Item = $ReadOnly<{|
4 | name: string,
5 | description: string,
6 | score: number,
7 | |}>
8 |
9 | export type SocialUser = $ReadOnly<{|
10 | id: number,
11 | extra_data: { access_token?: string },
12 | |}>
13 |
14 | export type User = $ReadOnly<{|
15 | id: number,
16 | stars: ?number,
17 | subscribed: boolean,
18 | username: string,
19 | first_name: string,
20 | last_name: string,
21 | email: string,
22 | social_auth: SocialUser[],
23 | |}>
24 |
25 | export type Subscribe = $ReadOnly<{|
26 | status: boolean,
27 | |}>
28 |
29 | export type PromiseStateType = $ReadOnly<{|
30 | pending: boolean,
31 | rejected: boolean,
32 | fulfilled: boolean,
33 | value: T,
34 | meta: {
35 | response: {
36 | status: number,
37 | },
38 | },
39 | |}>
40 |
--------------------------------------------------------------------------------
/src/components/SubscribeMessage/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { action } from '@storybook/addon-actions'
3 | import 'semantic-ui-css/semantic.min.css'
4 |
5 | import SubscribeMessage from './index'
6 |
7 | export default {
8 | title: 'Components|Message',
9 | component: SubscribeMessage,
10 | }
11 |
12 | const props = {
13 | onClick: action('clicked'),
14 | }
15 |
16 | export const defaultStory = () =>
17 | defaultStory.story = {
18 | name: 'default',
19 | }
20 |
21 | export const subscribed = () =>
22 |
23 | export const loading = () =>
24 |
25 | export const loadingSubscribed = () =>
26 | loadingSubscribed.story = {
27 | name: 'loading & subscribed',
28 | }
29 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import 'raf/polyfill'
4 | import 'whatwg-fetch'
5 | import { isNil } from 'ramda'
6 |
7 | import React from 'react'
8 | import { render } from 'react-snapshot'
9 | import Raven from 'raven-js'
10 | import ReactGA from 'react-ga'
11 |
12 | import App from './App'
13 |
14 | ReactGA.initialize(process.env.REACT_APP_GA, {
15 | debug: process.env.NODE_ENV !== 'production',
16 | })
17 |
18 | if (process.env.NODE_ENV === 'production') {
19 | const { REACT_APP_RAVEN_DSN, REACT_APP_VERSION } = process.env
20 |
21 | if (isNil(REACT_APP_RAVEN_DSN)) throw new Error('REACT_APP_RAVEN_DSN not found')
22 | if (isNil(REACT_APP_VERSION)) throw new Error('REACT_APP_VERSION not found')
23 |
24 | const config = {
25 | release: REACT_APP_VERSION,
26 | environment: 'production',
27 | }
28 |
29 | Raven.config(REACT_APP_RAVEN_DSN, config).install()
30 | }
31 |
32 | render( , document.getElementById('root'))
33 |
--------------------------------------------------------------------------------
/src/components/Statistics/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
7 |
10 |
13 | 19,925,204
14 |
15 |
18 | Users
19 |
20 |
21 |
24 |
27 | 64,245,061
28 |
29 |
32 | Repositories
33 |
34 |
35 |
38 |
41 | 81,302,007
42 |
43 |
46 | Stars
47 |
48 |
49 |
50 | `;
51 |
--------------------------------------------------------------------------------
/src/components/Recommendations/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MemoryRouter } from 'react-router-dom'
3 | import { action } from '@storybook/addon-actions'
4 | import 'semantic-ui-css/semantic.min.css'
5 |
6 | import Recommendations from './index'
7 | import { recommendations } from '../../utils/fixtures'
8 |
9 | export default {
10 | title: 'Pages|Recommendations',
11 | component: Recommendations,
12 | decorators: [story => {story()} ],
13 | }
14 |
15 | const user = {
16 | id: 0,
17 | username: 'yurtaev',
18 | first_name: '',
19 | last_name: '',
20 | email: '',
21 | social_auth: [],
22 | stars: 100,
23 | subscribed: false,
24 | }
25 |
26 | const props = {
27 | onSubscribe: action('subscribe'),
28 | user: user,
29 | }
30 |
31 | export const success = () =>
32 | export const error = () =>
33 | export const warning = () =>
34 |
--------------------------------------------------------------------------------
/src/components/RecommendationsList/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 |
4 | import RecommendationsList from './index'
5 |
6 | const items = [
7 | {
8 | name: 'FreeCodeCamp/FreeCodeCamp',
9 | score: 0.9888941549280906,
10 | description:
11 | 'The http://FreeCodeCamp.com open source codebase and curriculum. Learn to code and help nonprofits.',
12 | },
13 | {
14 | name: 'mxstbr/react-boilerplate',
15 | score: 0.9909724505140743,
16 | description:
17 | ':fire: Quick setup for performance orientated, offline-first React.js applications featuring Redux, hot-reloading, PostCSS, react-router, ServiceWorker, AppCache, FontFaceObserver and Mocha.',
18 | },
19 | {
20 | name: 'nodejs/node',
21 | score: 0.9905174541678359,
22 | description: 'Node.js JavaScript runtime :sparkles::turtle::rocket::sparkles:',
23 | },
24 | ]
25 |
26 | test(' ', () => {
27 | const component = renderer.create( )
28 | expect(component.toJSON()).toMatchSnapshot()
29 | })
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015, Vincent Garreau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/containers/SubscribeMessage/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import { connect } from 'react-refetch'
6 |
7 | import SubscribeMessage from '../../components/SubscribeMessage'
8 | import { API_SUBSCRIBE_URL } from '../../constants/index'
9 | import type { PromiseStateType, Subscribe } from '../../types'
10 |
11 | const refetch = connect.defaults({
12 | credentials: 'include',
13 | })
14 |
15 | const postSubscribe = () => ({
16 | postSubscribeResponse: {
17 | url: API_SUBSCRIBE_URL,
18 | method: 'POST',
19 | force: true,
20 | },
21 | })
22 |
23 | type Props = {
24 | subscribed: boolean,
25 | postSubscribe: (*) => void,
26 | postSubscribeResponse?: PromiseStateType,
27 | }
28 |
29 | export const SubscribeMessageContainer = ({ subscribed, postSubscribe, postSubscribeResponse }: Props) => {
30 | let pending = false
31 | let status = subscribed
32 |
33 | if (postSubscribeResponse) {
34 | pending = postSubscribeResponse.pending
35 | status = postSubscribeResponse.fulfilled ? postSubscribeResponse.value.status : subscribed
36 | }
37 | return
38 | }
39 |
40 | export default refetch(props => ({
41 | postSubscribe: postSubscribe,
42 | }))(SubscribeMessageContainer)
43 |
--------------------------------------------------------------------------------
/src/components/SubscribeMessage/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 | import { shallow } from 'enzyme'
4 |
5 | import SubscribeMessage from './index'
6 |
7 | const props = {
8 | onClick: jest.fn(),
9 | }
10 |
11 | describe(' ', () => {
12 | test(' ', () => {
13 | const component = renderer.create( )
14 | expect(component.toJSON()).toMatchSnapshot()
15 | })
16 |
17 | test(' ', () => {
18 | const component = renderer.create( )
19 | expect(component.toJSON()).toMatchSnapshot()
20 | })
21 |
22 | test(' ', () => {
23 | const component = renderer.create( )
24 | expect(component.toJSON()).toMatchSnapshot()
25 | })
26 |
27 | test(' ', () => {
28 | const component = renderer.create( )
29 | expect(component.toJSON()).toMatchSnapshot()
30 | })
31 |
32 | test('onClick', () => {
33 | const component = shallow( )
34 | component.find('Button').simulate('click')
35 | expect(props.onClick.mock.calls.length).toBe(1)
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/src/components/SubscribeMessage/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import { Message, Icon, Button } from 'semantic-ui-react'
6 |
7 | import { trackClick } from '../../utils'
8 |
9 | const getIcon = (loading: boolean): string => {
10 | return loading ? 'circle notched' : 'envelope outline'
11 | }
12 |
13 | type Props = {
14 | loading: boolean,
15 | subscribed: boolean,
16 | onClick: (*) => void,
17 | }
18 |
19 | const SubscribeMessage = ({ loading, subscribed, onClick }: Props) => {
20 | const handleClick = () => {
21 | onClick(subscribed)
22 | trackClick('Subscribe')()
23 | }
24 | const buttonSubscibe =
25 | const buttonUnsubscibe =
26 | return (
27 |
28 |
29 |
30 | Subscribe to get new recommendations as soon as they arrive.
31 |
32 | {!subscribed && buttonSubscibe}
33 | {subscribed && buttonUnsubscibe}
34 |
35 |
36 | )}
37 |
38 | SubscribeMessage.defaultProps = {
39 | loading: false,
40 | subscribed: false,
41 | }
42 |
43 | export default SubscribeMessage
44 |
--------------------------------------------------------------------------------
/src/components/RecommendationsList/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import { List } from 'semantic-ui-react'
6 | import Linkify from 'react-linkify'
7 | import { emojify } from 'react-emoji'
8 |
9 | import type { Item } from '../../types/index'
10 |
11 | const getUrl = (name: string) => `https://github.com/${name}`
12 |
13 | const sortItem = (a: Item, b: Item): number => b.score - a.score
14 |
15 | const renderListItemHeader = (item: Item) => (
16 |
21 | )
22 |
23 | const renderItem = (item: Item) => (
24 |
25 |
26 |
27 | {renderListItemHeader(item)}
28 |
29 |
30 | {emojify(item.description)}
31 |
32 |
33 |
34 |
35 | )
36 |
37 | type Props = {
38 | items: Item[],
39 | }
40 |
41 | const RecommendationsList = ({ items }: Props) => (
42 |
43 | {items.sort(sortItem).map(renderItem)}
44 |
45 | )
46 |
47 | export default RecommendationsList
48 |
--------------------------------------------------------------------------------
/src/containers/RecommendationsPage.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 | import React from 'react'
3 | import { connect, PromiseState } from 'react-refetch'
4 | import { Dimmer, Loader } from 'semantic-ui-react'
5 |
6 | import Footer from '../components/Footer'
7 | import Recommendations from '../components/Recommendations'
8 | import { API_RECOMMENDATIONS_URL, API_USER_URL, LOGIN_URL } from '../constants'
9 | import type { Item, User, PromiseStateType } from '../types/index'
10 |
11 | const refetch = connect.defaults({
12 | credentials: 'include',
13 | })
14 |
15 | const defaultUser: User = {
16 | id: 0,
17 | username: '/',
18 | first_name: '',
19 | last_name: '',
20 | email: '',
21 | social_auth: [],
22 | stars: 100,
23 | subscribed: false,
24 | }
25 |
26 | type Props = {
27 | dataFetch: PromiseStateType- ,
28 | userFetch: PromiseStateType
,
29 | }
30 |
31 | export const RecommendationsPage = ({ dataFetch, userFetch }: Props) => {
32 | const allFetches = PromiseState.all([dataFetch, userFetch])
33 | const { pending, rejected, fulfilled } = allFetches
34 | const [items, user] = fulfilled ? allFetches.value : [[], defaultUser]
35 | let redirect = false
36 | if (rejected && dataFetch.meta.response) {
37 | const { status } = dataFetch.meta.response
38 | if (status === 401 || status === 403) {
39 | window.location.replace(LOGIN_URL)
40 | redirect = true
41 | }
42 | }
43 | return (
44 |
45 |
46 |
47 |
48 | {!pending && !redirect && }
49 | {!pending && !redirect && }
50 |
51 | )
52 | }
53 |
54 | export default refetch(props => ({
55 | dataFetch: API_RECOMMENDATIONS_URL,
56 | userFetch: API_USER_URL,
57 | }))(RecommendationsPage)
58 |
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import { Container, Header, Segment, Grid, List, Icon } from 'semantic-ui-react'
6 | import { trackClick } from '../../utils'
7 |
8 | const Footer = () => (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 | Hire us
22 |
23 |
28 | GitHub
29 |
30 |
35 | Twitter
36 |
37 |
38 |
39 |
40 |
41 | Copyright © 2017 Andrey Lisin and{' '}
42 | Egor Yurtaev
43 |
44 | The code is licensed under MIT.
45 |
46 |
47 |
48 |
49 |
50 | )
51 |
52 | export default Footer
53 |
--------------------------------------------------------------------------------
/src/containers/SubscribeMessage/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderer from 'react-test-renderer'
3 | import { mount } from 'enzyme'
4 | import { PromiseState } from 'react-refetch'
5 |
6 | import { SubscribeMessageContainer } from './index'
7 |
8 | const props = {
9 | postSubscribe: jest.fn(),
10 | subscribed: false,
11 | }
12 |
13 | describe(' ', () => {
14 | test('loading', () => {
15 | const postSubscribeResponse = PromiseState.create()
16 | const component = renderer.create(
17 | ,
18 | )
19 | expect(component.toJSON()).toMatchSnapshot()
20 | })
21 |
22 | test('subscribed: false', () => {
23 | const component = renderer.create( )
24 | expect(component.toJSON()).toMatchSnapshot()
25 | })
26 |
27 | test('subscribed: true', () => {
28 | const component = renderer.create( )
29 | expect(component.toJSON()).toMatchSnapshot()
30 | })
31 |
32 | test('error', () => {
33 | const postSubscribeResponse = PromiseState.reject()
34 | const component = renderer.create(
35 | ,
36 | )
37 | expect(component.toJSON()).toMatchSnapshot()
38 | })
39 |
40 | test('success', () => {
41 | const postSubscribeResponse = PromiseState.resolve({ status: true })
42 | const component = renderer.create(
43 | ,
44 | )
45 | expect(component.toJSON()).toMatchSnapshot()
46 | })
47 |
48 | test('Click', () => {
49 | const component = mount( )
50 | component.find('Button').simulate('click')
51 | expect(props.postSubscribe.mock.calls.length).toBe(1)
52 | })
53 | })
54 |
--------------------------------------------------------------------------------
/src/components/Recommendations/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 | import React from 'react'
3 | import { Link } from 'react-router-dom'
4 |
5 | import RecommendationsList from '../RecommendationsList/index'
6 | import SubscribeMessage from '../../containers/SubscribeMessage/index'
7 | import { Container, Header, Message, Menu } from 'semantic-ui-react'
8 | import type { Item, User } from '../../types/index'
9 |
10 | const MessageWarning = () => (
11 |
15 | )
16 |
17 | const MessageError = () =>
18 |
19 | type Props = {
20 | error: boolean,
21 | items: Item[],
22 | user: User,
23 | loading?: boolean,
24 | }
25 |
26 | export const Recommendations = ({ loading, items, error, user }: Props) => {
27 | const showWarning = (user.stars || 0) <= 30
28 | return (
29 |
30 |
31 |
32 | Home
33 |
34 |
35 | Recommendations
36 |
37 |
41 | How it Works
42 |
43 |
44 |
50 | {showWarning && }
51 | {error && }
52 |
53 |
54 |
55 | )
56 | }
57 |
58 | export default Recommendations
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ghrecommender",
3 | "version": "0.1.0",
4 | "description": "GHRecommender - personalized recommendations for GitHub projects based on information about repositories starred by the user",
5 | "homepage": "https://ghrecommender.io",
6 | "license": "MIT",
7 | "contributors": [
8 | {
9 | "name": "Andrey Lisin",
10 | "url": "https://github.com/avli"
11 | },
12 | {
13 | "name": "Egor Yurtaev",
14 | "url": "https://github.com/yurtaev"
15 | }
16 | ],
17 | "private": true,
18 | "dependencies": {
19 | "raf": "^3.4.1",
20 | "ramda": "^0.26.1",
21 | "raven-js": "^3.27.2",
22 | "react": "^16.9.0",
23 | "react-dom": "^16.9.0",
24 | "react-emoji": "^0.5.0",
25 | "react-ga": "^2.6.0",
26 | "react-linkify": "^0.2.1",
27 | "react-particles-js": "^2.7.0",
28 | "react-refetch": "^2.0.0",
29 | "react-router-dom": "^5.0.1",
30 | "react-scripts": "3.1.0",
31 | "react-snapshot": "^1.3.0",
32 | "semantic-ui-css": "^2.2.12",
33 | "semantic-ui-react": "^0.87.3",
34 | "whatwg-fetch": "^3.0.0"
35 | },
36 | "scripts": {
37 | "start": "react-scripts start",
38 | "build": "react-scripts build && react-snapshot",
39 | "test": "react-scripts test --env=jsdom",
40 | "eject": "react-scripts eject",
41 | "storybook": "start-storybook -s ./public -p 6006",
42 | "build-storybook": "build-storybook",
43 | "flow": "flow check",
44 | "lint": "eslint ./src",
45 | "upload": "aws s3 sync build/ s3://ghrecommender.io --cache-control max-age=86400 --acl=public-read",
46 | "deploy": "yarn build && yarn upload",
47 | "postinstall": "flow-typed install"
48 | },
49 | "reactSnapshot": {
50 | "include": [
51 | "/",
52 | "/app/recommendations/"
53 | ],
54 | "snapshotDelay": 300
55 | },
56 | "devDependencies": {
57 | "@babel/core": "^7.5.5",
58 | "@babel/runtime": "^7.5.5",
59 | "@storybook/addon-actions": "^5.2.0-beta.28",
60 | "@storybook/addon-docs": "^5.2.0-beta.28",
61 | "@storybook/react": "5.2.0-beta.28",
62 | "babel-loader": "8.0.6",
63 | "enzyme": "^3.10.0",
64 | "enzyme-adapter-react-16": "^1.14.0",
65 | "flow-bin": "^0.105.1",
66 | "flow-typed": "^2.6.1",
67 | "react-test-renderer": "^16.9.0"
68 | },
69 | "browserslist": {
70 | "production": [
71 | ">0.2%",
72 | "not dead",
73 | "not op_mini all"
74 | ],
75 | "development": [
76 | "last 1 chrome version",
77 | "last 1 firefox version",
78 | "last 1 safari version"
79 | ]
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/SubscribeMessage/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
7 |
12 |
15 |
18 | Subscribe to get new recommendations as soon as they arrive.
19 |
20 |
21 |
25 | Subscribe
26 |
27 |
28 |
29 | `;
30 |
31 | exports[` 1`] = `
32 |
35 |
40 |
43 |
46 | Subscribe to get new recommendations as soon as they arrive.
47 |
48 |
49 |
53 | Subscribe
54 |
55 |
56 |
57 | `;
58 |
59 | exports[` 1`] = `
60 |
63 |
68 |
71 |
74 | Subscribe to get new recommendations as soon as they arrive.
75 |
76 |
77 |
81 | Unsubscribe
82 |
83 |
84 |
85 | `;
86 |
87 | exports[` 1`] = `
88 |
91 |
96 |
99 |
102 | Subscribe to get new recommendations as soon as they arrive.
103 |
104 |
105 |
109 | Unsubscribe
110 |
111 |
112 |
113 | `;
114 |
--------------------------------------------------------------------------------
/src/components/Footer/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
12 |
15 |
18 |
21 |
24 |
27 | About
28 |
29 |
73 |
74 |
96 |
97 |
98 |
99 |
100 | `;
101 |
--------------------------------------------------------------------------------
/src/components/Background/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import React from 'react'
4 |
5 | import Particles from 'react-particles-js'
6 |
7 | const color = '#5382C5'
8 | const count = window.innerWidth / 25 / window.devicePixelRatio
9 |
10 |
11 | const options = {
12 | particles: {
13 | number: {
14 | value: count,
15 | density: {
16 | enable: true,
17 | value_area: 800,
18 | },
19 | },
20 | color: {
21 | value: color,
22 | },
23 | shape: {
24 | type: 'circle',
25 | stroke: {
26 | width: 0,
27 | color: '#000000',
28 | },
29 | polygon: {
30 | nb_sides: 5,
31 | },
32 | },
33 | opacity: {
34 | value: 0.5,
35 | random: false,
36 | anim: {
37 | enable: false,
38 | speed: 1,
39 | opacity_min: 0.1,
40 | sync: false,
41 | },
42 | },
43 | size: {
44 | value: 5,
45 | random: true,
46 | anim: {
47 | enable: false,
48 | speed: 40,
49 | size_min: 0.1,
50 | sync: false,
51 | },
52 | },
53 | line_linked: {
54 | enable: true,
55 | distance: 150,
56 | color: color,
57 | opacity: 0.4,
58 | width: 1,
59 | },
60 | move: {
61 | enable: true,
62 | speed: 6,
63 | direction: 'none',
64 | random: false,
65 | straight: false,
66 | out_mode: 'bounce',
67 | bounce: false,
68 | attract: {
69 | enable: false,
70 | rotateX: 600,
71 | rotateY: 1200,
72 | },
73 | },
74 | },
75 | interactivity: {
76 | detect_on: 'window',
77 | onresize: {
78 | density_auto: true,
79 | },
80 | events: {
81 | onhover: {
82 | enable: true,
83 | mode: 'repulse',
84 | },
85 | onclick: {
86 | enable: true,
87 | mode: 'push',
88 | },
89 | resize: true,
90 | },
91 | modes: {
92 | grab: {
93 | distance: 400,
94 | line_linked: {
95 | opacity: 1,
96 | },
97 | },
98 | bubble: {
99 | distance: 400,
100 | size: 40,
101 | duration: 2,
102 | opacity: 8,
103 | speed: 3,
104 | },
105 | repulse: {
106 | distance: 200,
107 | duration: 0.4,
108 | },
109 | push: {
110 | particles_nb: 4,
111 | },
112 | remove: {
113 | particles_nb: 2,
114 | },
115 | },
116 | },
117 | retina_detect: true,
118 | }
119 |
120 | const Background = () => (
121 |
131 | )
132 |
133 | export default Background
134 |
--------------------------------------------------------------------------------
/src/containers/SubscribeMessage/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` error 1`] = `
4 |
7 |
12 |
15 |
18 | Subscribe to get new recommendations as soon as they arrive.
19 |
20 |
21 |
25 | Subscribe
26 |
27 |
28 |
29 | `;
30 |
31 | exports[` loading 1`] = `
32 |
35 |
40 |
43 |
46 | Subscribe to get new recommendations as soon as they arrive.
47 |
48 |
49 |
53 | Subscribe
54 |
55 |
56 |
57 | `;
58 |
59 | exports[` subscribed: false 1`] = `
60 |
63 |
68 |
71 |
74 | Subscribe to get new recommendations as soon as they arrive.
75 |
76 |
77 |
81 | Subscribe
82 |
83 |
84 |
85 | `;
86 |
87 | exports[` subscribed: true 1`] = `
88 |
91 |
96 |
99 |
102 | Subscribe to get new recommendations as soon as they arrive.
103 |
104 |
105 |
109 | Unsubscribe
110 |
111 |
112 |
113 | `;
114 |
115 | exports[` success 1`] = `
116 |
119 |
124 |
127 |
130 | Subscribe to get new recommendations as soon as they arrive.
131 |
132 |
133 |
137 | Unsubscribe
138 |
139 |
140 |
141 | `;
142 |
--------------------------------------------------------------------------------
/src/containers/HomePage/index.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 | import React from 'react'
3 |
4 | import { Button, Container, Header, Icon, Menu, Segment, Responsive } from 'semantic-ui-react'
5 | import { Link } from 'react-router-dom'
6 |
7 | import Footer from '../../components/Footer'
8 | import Background from '../../components/Background'
9 | import StatisticGroups from '../../components/Statistics'
10 | import { trackClick } from '../../utils'
11 |
12 | const logoStyle = {
13 | marginTop: '3em',
14 | userSelect: 'none',
15 | userDrag: 'none',
16 | webkitUserDrag: 'none',
17 | }
18 |
19 | const renderHeaders = (mobile: boolean) => {
20 | const fontSize1 = mobile ? '2em' : '4em'
21 | const fontSize2 = mobile ? '1.5em' : '1.7em'
22 | return (
23 |
24 |
25 |
31 |
37 |
38 | )
39 | }
40 |
41 | const HomePage = () => (
42 |
43 |
49 |
50 |
51 |
52 | Home
53 |
54 |
59 | How it Works
60 |
61 |
62 |
63 |
64 |
65 | {renderHeaders(false)}
66 | {renderHeaders(true)}
67 |
74 | Get Recommendations
75 |
76 |
77 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | )
98 |
99 | export default HomePage
100 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
30 | %REACT_APP_TITLE%
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
57 |
58 |
59 |
60 | You need to enable JavaScript to run this app.
61 |
62 |
63 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/components/RecommendationsList/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
162 | `;
163 |
--------------------------------------------------------------------------------
/src/containers/HomePage/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` 1`] = `
4 |
5 |
14 |
42 |
51 |
52 |
53 |
65 |
75 | GHRecommender
76 |
77 |
87 | Welcome to the GHRecommender project – the place where you can find the GitHub projects that may be of interest to you but you haven’t discovered yet.
88 |
89 |
90 |
91 |
97 | Get Recommendations
98 |
103 |
104 |
107 | Want to know more? Read check out the
108 |
109 |
113 | “How it Works”
114 |
115 |
116 | page.
117 |
118 |
119 |
128 |
131 |
134 |
137 | 19,925,204
138 |
139 |
142 | Users
143 |
144 |
145 |
148 |
151 | 64,245,061
152 |
153 |
156 | Repositories
157 |
158 |
159 |
162 |
165 | 81,302,007
166 |
167 |
170 | Stars
171 |
172 |
173 |
174 |
175 |
176 |
187 |
188 |
189 |
197 |
200 |
203 |
206 |
209 |
212 | About
213 |
214 |
258 |
259 |
281 |
282 |
283 |
284 |
285 |
286 | `;
287 |
--------------------------------------------------------------------------------
/public/assets/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
133 |
136 |
147 |
151 |
154 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/utils/fixtures.js:
--------------------------------------------------------------------------------
1 | // @flow strict
2 |
3 | import type { Item } from '../types/index'
4 |
5 | export const recommendations: Item[] = [
6 | {
7 | name: 'sindresorhus/awesome',
8 | score: 0.9979729813497394,
9 | description: 'A curated list of awesome lists',
10 | },
11 | {
12 | name: 'mzabriskie/axios',
13 | score: 0.9975339110778233,
14 | description: 'Promise based HTTP client for the browser and node.js',
15 | },
16 | {
17 | name: 'vuejs/vue',
18 | score: 0.9965397729495139,
19 | description: 'Simple yet powerful library for building modern web interfaces.',
20 | },
21 | {
22 | name: 'getify/You-Dont-Know-JS',
23 | score: 0.996483659760743,
24 | description: 'A book series on JavaScript. @YDKJS on twitter.',
25 | },
26 | {
27 | name: 'robbyrussell/oh-my-zsh',
28 | score: 0.9963925530112941,
29 | description:
30 | 'A delightful community-driven (with 1,000+ contributors) framework for managing your zsh configuration. Includes 200+ optional plugins (rails, git, OSX, hub, capistrano, brew, ant, php, python, etc), over 140 themes to spice up your morning, and an auto-u',
31 | },
32 | {
33 | name: 'angular/angular-seed',
34 | score: 0.9961933999046063,
35 | description: 'Seed project for angular apps. ',
36 | },
37 | {
38 | name: 'developit/preact',
39 | score: 0.9958395662099023,
40 | description:
41 | ':zap: Fast 3kb React alternative with the same ES6 API. Components & Virtual DOM.',
42 | },
43 | {
44 | name: 'tensorflow/tensorflow',
45 | score: 0.9958274065375602,
46 | description: 'Computation using data flow graphs for scalable machine learning',
47 | },
48 | {
49 | name: 'airbnb/javascript',
50 | score: 0.9957700418535849,
51 | description: 'JavaScript Style Guide',
52 | },
53 | {
54 | name: 'typicode/json-server',
55 | score: 0.995106258245427,
56 | description: 'Get a full fake REST API with zero coding in less than 30 seconds (seriously)',
57 | },
58 | {
59 | name: 'resume/resume.github.com',
60 | score: 0.9947942387875661,
61 | description: 'Resumes generated using the GitHub informations',
62 | },
63 | {
64 | name: 'nodesource/distributions',
65 | score: 0.9942333298881096,
66 | description: 'NodeSource Node.js Binary Distributions',
67 | },
68 | {
69 | name: 'verekia/js-stack-from-scratch',
70 | score: 0.9941201776411874,
71 | description: 'JavaScript Stack From Scratch',
72 | },
73 | {
74 | name: 'facebook/immutable-js',
75 | score: 0.9940453335854237,
76 | description:
77 | 'Immutable persistent data collections for Javascript which increase efficiency and simplicity.',
78 | },
79 | {
80 | name: 'kamranahmedse/design-patterns-for-humans',
81 | score: 0.9939301032368906,
82 | description: 'Design Patterns - An ultra-simplified explanation to design patterns',
83 | },
84 | {
85 | name: 'nvbn/thefuck',
86 | score: 0.9936853333633191,
87 | description: 'Magnificent app which corrects your previous console command.',
88 | },
89 | {
90 | name: 'mholt/caddy',
91 | score: 0.9935959908343273,
92 | description: 'Fast, cross-platform HTTP/2 web server with automatic HTTPS',
93 | },
94 | {
95 | name: 'alex/what-happens-when',
96 | score: 0.9935734139897099,
97 | description:
98 | 'An attempt to answer the age old interview question "What happens when you type google.com into your browser and press enter?"',
99 | },
100 | {
101 | name: 'graphql-python/graphene',
102 | score: 0.9932396690526899,
103 | description: 'GraphQL framework for Python',
104 | },
105 | {
106 | name: 'lukehoban/es6features',
107 | score: 0.9932118786181094,
108 | description: 'Overview of ECMAScript 6 features',
109 | },
110 | {
111 | name: 'begriffs/postgrest',
112 | score: 0.993198240467652,
113 | description: 'REST API for any Postgres database',
114 | },
115 | {
116 | name: 'atlassian/localstack',
117 | score: 0.9928572754072205,
118 | description:
119 | 'A fully functional local AWS cloud stack. Develop and test your cloud apps offline!',
120 | },
121 | {
122 | name: 'webpack/webpack',
123 | score: 0.9928442377275571,
124 | description:
125 | 'A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows to load parts for the application on demand. Through "loaders," modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ...',
126 | },
127 | {
128 | name: 'jgthms/bulma',
129 | score: 0.9927337802181523,
130 | description: 'Modern CSS framework based on Flexbox',
131 | },
132 | {
133 | name: 'facebook/draft-js',
134 | score: 0.9926089284033656,
135 | description: 'A React framework for building text editors.',
136 | },
137 | {
138 | name: 'Mashape/kong',
139 | score: 0.9924710809635343,
140 | description: ':monkey: Open-source, Microservice & API Management Layer built on top of NGINX',
141 | },
142 | {
143 | name: 'chenglou/react-motion',
144 | score: 0.9924622965535776,
145 | description: 'A spring that solves your animation problems.',
146 | },
147 | {
148 | name: 'avelino/awesome-go',
149 | score: 0.9924366840307954,
150 | description: 'A curated list of awesome Go frameworks, libraries and software',
151 | },
152 | {
153 | name: 'cockroachdb/cockroach',
154 | score: 0.9923966137933203,
155 | description: 'A Scalable, Geo-Replicated, Transactional Datastore',
156 | },
157 | {
158 | name: 'jlevy/the-art-of-command-line',
159 | score: 0.9920574687528032,
160 | description: 'Master the command line, in one page',
161 | },
162 | {
163 | name: 'serverless/serverless',
164 | score: 0.9915701118820088,
165 | description:
166 | 'Serverless Framework – Build and maintain web, mobile and IoT applications running on AWS Lambda and API Gateway (formerly known as JAWS) – ',
167 | },
168 | {
169 | name: 'davezuko/react-redux-starter-kit',
170 | score: 0.9915096606254822,
171 | description: 'Get started with React, Redux, and React-Router!',
172 | },
173 | {
174 | name: 'channelcat/sanic',
175 | score: 0.9914509704574478,
176 | description: "Python 3.5+ web server that's written to go fast",
177 | },
178 | {
179 | name: 'herrbischoff/awesome-osx-command-line',
180 | score: 0.9913750513902113,
181 | description: 'Use your OS X terminal shell to do awesome things.',
182 | },
183 | {
184 | name: 'grafana/grafana',
185 | score: 0.9913354204873708,
186 | description: 'Gorgeous metric viz, dashboards & editors for Graphite, InfluxDB & OpenTSDB',
187 | },
188 | {
189 | name: 'Reactive-Extensions/RxJS',
190 | score: 0.9912699221140397,
191 | description: 'The Reactive Extensions for JavaScript',
192 | },
193 | {
194 | name: 'jwasham/google-interview-university',
195 | score: 0.9910622820225498,
196 | description: 'A complete daily plan for studying to become a Google software engineer.',
197 | },
198 | {
199 | name: 'mxstbr/react-boilerplate',
200 | score: 0.9909724505140743,
201 | description:
202 | ':fire: Quick setup for performance orientated, offline-first React.js applications featuring Redux, hot-reloading, PostCSS, react-router, ServiceWorker, AppCache, FontFaceObserver and Mocha.',
203 | },
204 | {
205 | name: 'graphql/graphql-js',
206 | score: 0.9908298578416967,
207 | description: 'A reference implementation of GraphQL for JavaScript',
208 | },
209 | {
210 | name: 'rethinkdb/horizon',
211 | score: 0.9906603024337165,
212 | description: 'A developer platform for building engaging, realtime, and scalable web apps.',
213 | },
214 | {
215 | name: 'ansible/ansible',
216 | score: 0.9906264951909283,
217 | description:
218 | 'Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy. Avoid writing scripts or custom code to deploy and update your applications— automate in a language that approaches plain English, using SSH, w',
219 | },
220 | {
221 | name: 'spf13/hugo',
222 | score: 0.9905978798122961,
223 | description: 'A Fast and Flexible Static Site Generator built with love in GoLang',
224 | },
225 | {
226 | name: 'nodejs/node',
227 | score: 0.9905174541678359,
228 | description: 'Node.js JavaScript runtime :sparkles::turtle::rocket::sparkles:',
229 | },
230 | {
231 | name: 'golang/go',
232 | score: 0.9905115807209759,
233 | description: 'The Go programming language',
234 | },
235 | {
236 | name: 'FormidableLabs/webpack-dashboard',
237 | score: 0.9904423164260322,
238 | description: 'A CLI dashboard for webpack dev server',
239 | },
240 | {
241 | name: 'erikras/react-redux-universal-hot-example',
242 | score: 0.990359762729841,
243 | description:
244 | 'A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform',
245 | },
246 | {
247 | name: 'jkbrzt/httpie',
248 | score: 0.9902496937595102,
249 | description:
250 | 'CLI HTTP client; user-friendly cURL replacement featuring intuitive UI, JSON support, syntax highlighting, wget-like downloads, extensions, etc.',
251 | },
252 | {
253 | name: 'xgrommx/awesome-redux',
254 | score: 0.9902019178788738,
255 | description: 'Awesome list of Redux examples and middlewares',
256 | },
257 | {
258 | name: 'jwilder/nginx-proxy',
259 | score: 0.9901563086950389,
260 | description: 'Automated nginx proxy for Docker containers using docker-gen',
261 | },
262 | {
263 | name: 'minio/minio',
264 | score: 0.9900931755064731,
265 | description:
266 | 'Minio is an object storage server compatible with Amazon S3 and licensed under Apache 2.0 License',
267 | },
268 | {
269 | name: 'github/fetch',
270 | score: 0.9900415145362973,
271 | description: 'A window.fetch JavaScript polyfill.',
272 | },
273 | {
274 | name: 'kataras/iris',
275 | score: 0.9899365077042256,
276 | description:
277 | 'A very minimal but flexible golang web application framework, providing a robust set of features for building single & multi-page, web applications.',
278 | },
279 | {
280 | name: 'airbnb/react-native-maps',
281 | score: 0.9898676615204726,
282 | description: 'React Native Mapview component for iOS + Android',
283 | },
284 | {
285 | name: 'feross/standard',
286 | score: 0.9897306470230851,
287 | description: ':star2: JavaScript Standard Style',
288 | },
289 | {
290 | name: 'rethinkdb/rethinkdb',
291 | score: 0.9895085431650859,
292 | description: 'The open-source database for the realtime web.',
293 | },
294 | {
295 | name: 'sdmg15/Best-websites-a-programmer-should-visit',
296 | score: 0.9893771136688506,
297 | description: 'Some useful websites for programmers.',
298 | },
299 | {
300 | name: 'hemanth/functional-programming-jargon',
301 | score: 0.9892974714817625,
302 | description: 'Jargon from the functional programming world in simple terms! (WIP)',
303 | },
304 | {
305 | name: 'callemall/material-ui',
306 | score: 0.9892910778830325,
307 | description: "React Components that Implement Google's Material Design.",
308 | },
309 | {
310 | name: 'rg3/youtube-dl',
311 | score: 0.989235604674284,
312 | description: 'Command-line program to download videos from YouTube.com and other video sites',
313 | },
314 | {
315 | name: 'lodash/lodash',
316 | score: 0.9892262375297282,
317 | description:
318 | 'A modern JavaScript utility library delivering modularity, performance, & extras.',
319 | },
320 | {
321 | name: 'caesar0301/awesome-public-datasets',
322 | score: 0.9891878519095002,
323 | description: 'An awesome list of high-quality open datasets in public domains (on-going).',
324 | },
325 | {
326 | name: 'alexellis/faas',
327 | score: 0.9889979540192658,
328 | description: 'Functions as a service',
329 | },
330 | {
331 | name: 'gin-gonic/gin',
332 | score: 0.9889685120985483,
333 | description:
334 | 'Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.',
335 | },
336 | {
337 | name: 'FreeCodeCamp/FreeCodeCamp',
338 | score: 0.9888941549280906,
339 | description:
340 | 'The http://FreeCodeCamp.com open source codebase and curriculum. Learn to code and help nonprofits.',
341 | },
342 | {
343 | name: 'ZuzooVn/machine-learning-for-software-engineers',
344 | score: 0.9888616341300531,
345 | description: 'A complete daily plan for studying to become a machine learning engineer.',
346 | },
347 | {
348 | name: 'chentsulin/awesome-graphql',
349 | score: 0.9888338587295732,
350 | description: 'Awesome list of GraphQL & Relay',
351 | },
352 | {
353 | name: 'graphcool/chromeless',
354 | score: 0.9888026100558405,
355 | description: '???? Chrome automation made simple. Runs locally or headless on AWS Lambda.',
356 | },
357 | {
358 | name: 'minimaxir/big-list-of-naughty-strings',
359 | score: 0.9886200395405342,
360 | description:
361 | 'The Big List of Naughty Strings is a list of strings which have a high probability of causing issues when used as user-input data.',
362 | },
363 | {
364 | name: 'deepstreamIO/deepstream.io',
365 | score: 0.9885943518716591,
366 | description: 'deepstream.io node server',
367 | },
368 | {
369 | name: 'go-kit/kit',
370 | score: 0.9885937496761116,
371 | description: 'A standard library for microservices.',
372 | },
373 | {
374 | name: 'paularmstrong/normalizr',
375 | score: 0.988422424910656,
376 | description: 'Normalizes nested JSON according to a schema',
377 | },
378 | {
379 | name: 'enaqx/awesome-react',
380 | score: 0.988257004883754,
381 | description: 'A collection of awesome things regarding React ecosystem.',
382 | },
383 | {
384 | name: 'josephmisiti/awesome-machine-learning',
385 | score: 0.9882508608429654,
386 | description: 'A curated list of awesome Machine Learning frameworks, libraries and software.',
387 | },
388 | {
389 | name: 'Unitech/pm2',
390 | score: 0.9882350987712364,
391 | description: 'Production process manager for Node.js apps with a built-in load balancer',
392 | },
393 | {
394 | name: 'mobxjs/mobx',
395 | score: 0.988226646805767,
396 | description: 'Simple, scalable state management.',
397 | },
398 | {
399 | name: 'naptha/tesseract.js',
400 | score: 0.9882164121275739,
401 | description: 'nothing to see here',
402 | },
403 | {
404 | name: 'gaearon/redux-thunk',
405 | score: 0.9882054795195337,
406 | description: 'Thunk middleware for Redux',
407 | },
408 | {
409 | name: 'ParsePlatform/parse-server',
410 | score: 0.988195154347444,
411 | description: 'Parse-compatible API server module for Node/Express',
412 | },
413 | {
414 | name: 'ptmt/react-native-desktop',
415 | score: 0.9876718742501378,
416 | description: 'React Native for OS X ',
417 | },
418 | {
419 | name: 'zenorocha/clipboard.js',
420 | score: 0.9875825847512977,
421 | description: ':scissors: Modern copy to clipboard. No Flash. Just 2kb :clipboard:',
422 | },
423 | {
424 | name: 'uWebSockets/uWebSockets',
425 | score: 0.9874716673152757,
426 | description: 'Highly scalable WebSocket server library',
427 | },
428 | {
429 | name: 'meteor/meteor',
430 | score: 0.9874139135809074,
431 | description: 'Meteor, the JavaScript App Platform',
432 | },
433 | {
434 | name: 'atom/atom',
435 | score: 0.9872452987560052,
436 | description: 'The hackable text editor',
437 | },
438 | {
439 | name: 'junegunn/fzf',
440 | score: 0.9872096138005735,
441 | description: ':cherry_blossom: A command-line fuzzy finder written in Go',
442 | },
443 | {
444 | name: 'fogleman/primitive',
445 | score: 0.9870673407868598,
446 | description: 'Reproducing images with geometric primitives.',
447 | },
448 | {
449 | name: 'jwilm/alacritty',
450 | score: 0.9870467809840097,
451 | description: 'Exceptional speed and beautiful font rendering on all platforms',
452 | },
453 | {
454 | name: 'recharts/recharts',
455 | score: 0.9869767096378842,
456 | description: 'Re-designed charting library built with React',
457 | },
458 | {
459 | name: 'thejameskyle/the-super-tiny-compiler',
460 | score: 0.9869171846892538,
461 | description: 'Possibly the smallest compiler ever',
462 | },
463 | {
464 | name: 'bevacqua/dragula',
465 | score: 0.9868660344504577,
466 | description: ':ok_hand: Drag and drop so simple it hurts',
467 | },
468 | {
469 | name: 'wsargent/docker-cheat-sheet',
470 | score: 0.9868326674019124,
471 | description: 'Docker Cheat Sheet',
472 | },
473 | {
474 | name: 'JedWatson/react-select',
475 | score: 0.9867656884212473,
476 | description: 'A Select control built with and for React JS',
477 | },
478 | {
479 | name: 'koajs/koa',
480 | score: 0.9867552789088241,
481 | description: 'Expressive middleware for node.js using generators',
482 | },
483 | {
484 | name: 'facebook/relay',
485 | score: 0.9867015772175368,
486 | description: 'Relay is a JavaScript framework for building data-driven React applications.',
487 | },
488 | ]
489 |
--------------------------------------------------------------------------------