├── .gitignore
├── .babelrc
├── README.md
├── src
├── app
│ ├── entrypoints
│ │ └── main.js
│ ├── ducks
│ │ ├── index.js
│ │ └── searchResults.js
│ ├── store
│ │ └── configureStore.js
│ ├── containers
│ │ ├── root
│ │ │ └── Root.js
│ │ └── app
│ │ │ └── App.js
│ └── components
│ │ ├── searchresult
│ │ └── SearchResult.js
│ │ ├── searchinput
│ │ └── SearchInput.js
│ │ └── searchresults
│ │ └── SearchResults.js
├── index.html
└── utils
│ └── GITHUBAPIUtils.js
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | dist/
3 | html/
4 | node_modules/
5 | specification/
6 | *.iml
7 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "optional": [
4 | "runtime"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | reactjs-and-cutting-edge-stack
2 |
3 | Как запустить сервер для разработки:
4 | $ npm install
5 | $ npm run dev
6 |
--------------------------------------------------------------------------------
/src/app/entrypoints/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-dom";
3 | import Root from "containers/root/Root";
4 |
5 | render(, document.getElementById("container"));
6 |
--------------------------------------------------------------------------------
/src/app/ducks/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import searchResults from 'ducks/searchResults';
3 |
4 | const rootReducer = combineReducers({
5 | searchResults
6 | });
7 |
8 | export default rootReducer;
9 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Github search
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import rootReducer from "ducks";
3 | import thunk from "redux-thunk";
4 | import createLogger from "redux-logger";
5 |
6 | const createStoreWithMiddlewares = applyMiddleware(
7 | thunk,
8 | createLogger()
9 | )(createStore);
10 |
11 | export default createStoreWithMiddlewares(rootReducer);
12 |
--------------------------------------------------------------------------------
/src/app/containers/root/Root.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Provider } from "react-redux";
3 | import store from "store/configureStore";
4 | import App from "containers/app/App";
5 |
6 | export default class Root extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/components/searchresult/SearchResult.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from "react";
2 |
3 | export default class SearchResult extends Component {
4 |
5 | static propTypes = {
6 | result: PropTypes.object.isRequired
7 | }
8 |
9 | render() {
10 | const {
11 | result: {
12 | owner: { login: authorName },
13 | html_url: repositoryPath,
14 | full_name: repositoryName
15 | }
16 | } = this.props;
17 |
18 | return (
19 |
20 | {authorName}
21 | {repositoryName}
22 |
23 | );
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/components/searchinput/SearchInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from "react";
2 | import emptyFunction from "fbjs/lib/emptyFunction";
3 |
4 | export default class SearchInput extends Component {
5 |
6 | static propTypes = {
7 | onSearch: PropTypes.func
8 | }
9 |
10 | static defaultProps = {
11 | onSearch: emptyFunction
12 | }
13 |
14 | render() {
15 | return (
16 |
24 | );
25 | }
26 |
27 | onFormSubmit = (evt) => {
28 | evt.preventDefault();
29 | this.props.onSearch(this.input.value)
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/GITHUBAPIUtils.js:
--------------------------------------------------------------------------------
1 | import { fetch, Headers } from "whatwg-fetch";
2 |
3 | export default {
4 |
5 | async search(term) {
6 | const headers = new Headers();
7 | headers.append("Accept", "application/json");
8 | headers.append("Content-Type", "application/json");
9 |
10 | const query = JSON.stringify({ q: term });
11 |
12 | let response = await fetch(
13 | "https://api.github.com/search/repositories?q=" + encodeURIComponent(term),
14 | {
15 | method: "GET",
16 | headers
17 | }
18 | );
19 |
20 | if (response.status < 200 || response.status >= 300) {
21 | throw new Error(response.statusText);
22 | }
23 |
24 | response = await response.json();
25 |
26 | return response.items;
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/app/ducks/searchResults.js:
--------------------------------------------------------------------------------
1 | import { search as searchAPI } from "utils/GITHUBAPIUtils";
2 |
3 | const SEARCH = "RCES/searchResults/SEARCH";
4 |
5 | const initialState = {
6 | isLoading: false,
7 | results: null
8 | };
9 |
10 | export default function reducer(state = initialState, action = {}) {
11 | const { type, payload } = action;
12 |
13 | switch(type) {
14 | case SEARCH:
15 | return Object.assign(
16 | {},
17 | state,
18 | payload ? {
19 | results: payload,
20 | isLoading: false
21 | }
22 | :
23 | {
24 | results: [],
25 | isLoading: true
26 | }
27 | );
28 |
29 | default:
30 | return state;
31 | }
32 | }
33 |
34 | export function search(query) {
35 | return async function(dispatch, getState) {
36 | dispatch({
37 | type: SEARCH
38 | });
39 |
40 | const results = await searchAPI(query);
41 |
42 | return dispatch({
43 | type: SEARCH,
44 | payload: results
45 | });
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/components/searchresults/SearchResults.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from "react";
2 | import shallowCompare from "react-addons-shallow-compare";
3 | import SearchResult from "components/searchresult/SearchResult";
4 |
5 | export default class SearchResults extends Component {
6 |
7 | static propTypes = {
8 | results: PropTypes.array
9 | }
10 |
11 | static defaultProps = {
12 | results: []
13 | }
14 |
15 | shouldComponentUpdate = (nextProps, nextState) => shallowCompare(this, nextProps, nextState)
16 |
17 | render() {
18 | const {
19 | results
20 | } = this.props;
21 |
22 | if (!results) {
23 | return null;
24 | } else if (results.length > 0) {
25 | return (
26 |
27 | {
28 | results.map((result) =>
29 |
30 | )
31 | }
32 |
33 | );
34 | } else {
35 | return (
36 | There is no repositories with such name
37 | );
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/app/containers/app/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from "react";
2 | import { connect } from "react-redux";
3 | import SearchInput from "components/searchinput/SearchInput";
4 | import SearchResults from "components/searchresults/SearchResults";
5 | import { search } from "ducks/searchResults";
6 |
7 | class App extends Component {
8 |
9 | static propTypes = {
10 | results: PropTypes.array,
11 | isLoading: PropTypes.bool.isRequired
12 | }
13 |
14 | constructor(...args) {
15 | super(...args);
16 |
17 | this.onSearchInputSearch = this.onSearchInputSearch.bind(this);
18 | }
19 |
20 | render() {
21 | const {
22 | results,
23 | isLoading
24 | } = this.props;
25 |
26 | return (
27 |
28 |
29 | {isLoading ?
30 |
Loading...
31 | :
32 |
33 | }
34 |
35 | );
36 | }
37 |
38 | onSearchInputSearch(query) {
39 | this.props.dispatch(search(query));
40 | }
41 |
42 | }
43 |
44 | const mapStateToProps = ({ searchResults: { results, isLoading } }) => ({
45 | results,
46 | isLoading
47 | });
48 |
49 | export default connect(mapStateToProps)(App);
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RCES",
3 | "version": "0.0.1",
4 | "description": "React & cutting edge stack hackathon",
5 | "scripts": {
6 | "clean": "rimraf ./dist",
7 | "prebuild:html": "npm run clean",
8 | "build:html": "mkdirp dist && cpy ./src/index.html ./dist/",
9 | "build:webpack": "webpack -p --progress --colors",
10 | "build": "npm run build:html && npm run build:webpack",
11 | "predev": "npm run build:html",
12 | "dev": "webpack-dev-server --hot --content-base ./dist/ --port 4000 --history-api-fallback --inline"
13 | },
14 | "dependencies": {
15 | "classnames": "^2.1.0",
16 | "react": "^0.14.2",
17 | "react-addons-shallow-compare": "^0.14.2",
18 | "react-dom": "^0.14.2",
19 | "react-redux": "^4.0.0",
20 | "redux": "^3.0.4",
21 | "redux-logger": "^2.0.4",
22 | "redux-thunk": "^1.0.0",
23 | "whatwg-fetch": "^0.9.0"
24 | },
25 | "devDependencies": {
26 | "autoprefixer-loader": "^3.1.0",
27 | "babel-core": "^5.2.17",
28 | "babel-loader": "^5.0.0",
29 | "babel-runtime": "^5.2.17",
30 | "cpy": "^3.4.1",
31 | "css-loader": "^0.19.0",
32 | "exports-loader": "^0.6.2",
33 | "file-loader": "^0.8.1",
34 | "mkdirp": "^0.5.1",
35 | "react-hot-loader": "^1.2.7",
36 | "rimraf": "^2.3.3",
37 | "style-loader": "^0.12.2",
38 | "stylus": "^0.52.0",
39 | "stylus-loader": "^1.1.1",
40 | "url-loader": "^0.5.5",
41 | "webpack": "^1.9.4",
42 | "webpack-dev-server": "^1.8.2",
43 | "yargs": "^3.8.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var yargs = require("yargs");
3 | var path = require("path");
4 |
5 | var argv = yargs
6 | .boolean("p").alias("p", "optimize-minimize")
7 | .boolean("h").alias("h", "hot")
8 | .argv;
9 |
10 | module.exports = {
11 |
12 | entry: {
13 | main: path.join(__dirname, "src", "app", "entrypoints", "main.js")
14 | },
15 |
16 | output: {
17 | path: path.join(__dirname, "dist", "app"),
18 | filename: "[name].js",
19 | publicPath: "/app/"
20 | },
21 |
22 | module: {
23 | loaders: [
24 | {
25 | test: /fetch.js$/,
26 | loaders: ["exports?fetch=window.fetch.bind(window),Headers=window.Headers,Request=window.Request,Response=window.Response"]
27 | },
28 |
29 | {
30 | test: /\.js$/,
31 | exclude: [/node_modules/],
32 | loaders: (function() {
33 | var loaders = [];
34 |
35 | if (argv.h) {
36 | loaders.push("react-hot");
37 | }
38 |
39 | loaders.push("babel");
40 |
41 | return loaders;
42 | })()
43 | },
44 |
45 | {
46 | test: /\.styl$/,
47 | loaders: [
48 | "style",
49 | "css",
50 | "autoprefixer?browsers=last 2 version",
51 | "stylus"
52 | ]
53 | },
54 |
55 | {
56 | test: /\.css$/,
57 | loaders: [
58 | "style",
59 | "css",
60 | "autoprefixer?browsers=last 2 version"
61 | ]
62 | },
63 |
64 | {
65 | test: /\.(png|jpg|gif)$/,
66 | loaders: (function() {
67 | var loaders = [];
68 |
69 | loaders.push("url?limit=50000");
70 |
71 | return loaders;
72 | })()
73 | },
74 |
75 | {
76 | test: /\.(ttf|eot|woff|svg)$/,
77 | loaders: [
78 | "file"
79 | ]
80 | }
81 | ]
82 | },
83 |
84 | resolve: {
85 | extensions: ["", ".js"],
86 | root: [
87 | path.join(__dirname, "src", "app")
88 | ],
89 | modulesDirectories: ["web_modules", "node_modules", "src"]
90 | },
91 |
92 | cache: !argv.p,
93 | debug: !argv.p,
94 | devtool: !argv.p ? "source-map" : false,
95 |
96 | stats: {
97 | colors: true,
98 | reasons: !argv.p
99 | },
100 |
101 | plugins: (function() {
102 | var plugins = [];
103 |
104 | plugins.push(
105 | new webpack.DefinePlugin({
106 | "process.env.NODE_ENV": JSON.stringify(argv.p ? "production" : "development"),
107 | "__DEV__": !argv.p
108 | })
109 | );
110 |
111 | if (argv.p) {
112 | plugins.push(new webpack.optimize.DedupePlugin());
113 | plugins.push(new webpack.optimize.UglifyJsPlugin({
114 | compress: {
115 | warnings: false
116 | }
117 | }));
118 | plugins.push(new webpack.optimize.OccurenceOrderPlugin());
119 | plugins.push(new webpack.optimize.AggressiveMergingPlugin());
120 | }
121 |
122 | if (argv.h) {
123 | plugins.push(new webpack.NoErrorsPlugin());
124 | }
125 |
126 | return plugins;
127 | })()
128 | };
129 |
--------------------------------------------------------------------------------