├── .eslintrc.json
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── actions
└── index.js
├── components
├── App.js
├── character-list-item.js
├── character-list.js
├── details.js
└── search-bar.js
├── index.js
├── reducers
├── first_reducer.js
└── index.js
├── registerServiceWorker.js
└── style
└── style.css
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "plugins": ["no-unused-vars-rest"],
5 | "env": {
6 | "browser": true
7 | },
8 | "rules": {
9 | "semi": ["error", "never"],
10 | "comma-dangle": [2, "always-multiline"],
11 | "max-len": 0,
12 | "no-shadow": 0,
13 | "arrow-body-style": 0,
14 | "global-require": 0,
15 | "no-unused-expressions": 0,
16 | "no-confusing-arrow": 0,
17 | "no-unused-vars": 0,
18 | "no-constant-condition": 0,
19 | "import/no-dynamic-require": 0,
20 | "import/no-extraneous-dependencies": 0,
21 | "import/prefer-default-export": 0,
22 | "react/require-default-props": 0,
23 | "react/forbid-prop-types": 0,
24 | "react/no-unused-prop-types": 0,
25 | "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx"] }],
26 | "no-unused-vars-rest/no-unused-vars": [2, { "ignoreDestructuredVarsWithRest": true }]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "prettier.printWidth": 100,
4 | "prettier.singleQuote": true,
5 | "prettier.trailingComma": "all"
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ReactMarvelCharacterTutorial
2 |
3 |
React öğrenmek için geliştirilmiş örnek uygulama.
4 |
5 | ## Türkçe ayrıntılı anlatım makaleleri:
6 |
7 | ### part1: https://medium.com/t%C3%BCrkiye/react-tutorial-marvel-karakterleri-part-1-ortam-haz%C4%B1rl%C4%B1%C4%9F%C4%B1-boilerplate-ve-proje-analizi-cd735e2e3d30
8 |
9 | ### part2: https://medium.com/@muratturkaym/react-tutorial-part-2-marvel-karakterleri-kod1-e0df78c95ef5
10 |
11 | ### part3: https://medium.com/@muratturkaym/react-tutorial-part-3-marvel-karakterleri-5115111fac71
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "murat-turkay",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.16.2",
7 | "bootstrap": "^3.3.7",
8 | "history": "^4.6.3",
9 | "jquery": "^3.2.1",
10 | "lodash": "^4.17.4",
11 | "md5": "^2.2.1",
12 | "react": "^15.6.1",
13 | "react-dom": "^15.6.1",
14 | "react-hot-loader": "^3.0.0-beta.7",
15 | "react-redux": "^5.0.5",
16 | "react-router": "^4.1.2",
17 | "react-router-dom": "^4.1.2",
18 | "react-router-redux": "^4.0.8",
19 | "redux": "^3.7.2"
20 | },
21 | "devDependencies": {
22 | "eslint": "^4.3.0",
23 | "eslint-config-airbnb": "^15.1.0",
24 | "eslint-plugin-import": "^2.7.0",
25 | "eslint-plugin-jsx-a11y": "^5.1.1",
26 | "eslint-plugin-react": "^7.1.0",
27 | "prettier-eslint": "^6.4.2",
28 | "react-scripts": "1.0.10"
29 | },
30 | "scripts": {
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "lint:eslint": "eslint ./src",
34 | "test": "react-scripts test --env=jsdom",
35 | "eject": "react-scripts eject"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrTurkay/ReactMarvelCharacterTutorial/0965feb80cf5cf3f2838d297c71997dd1d5cf698/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | export function FirstAction() {
2 | return {
3 | type: "FIRST_ACTION"
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import SearchBar from './search-bar';
3 | import CharacterList from './character-list';
4 | import Details from './details';
5 |
6 | import md5 from 'md5';
7 | import $ from 'jquery';
8 |
9 | const API_URL = 'https://gateway.marvel.com:443/v1/public/';
10 | const publicKey = 'bad96750a8bcbcc02968526fed5c6f1d';
11 | const privateKey = '3c8d349aa883d4233a20530b8dbb15fc0e0543e0';
12 | const ts = '1';
13 | const auth = `ts=${ts}&apikey=${publicKey}&hash=${md5(`${ts}${privateKey}${publicKey}`)}`;
14 |
15 | class App extends Component {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {
20 | characters: null,
21 | selectedCharacter: null,
22 | };
23 |
24 | this.CharacterSearch = this.CharacterSearch.bind(this);
25 | }
26 |
27 | componentDidMount = () => {
28 | this.GetInitialChararcters();
29 | };
30 |
31 | GetInitialChararcters() {
32 | $.getJSON(`${API_URL}/characters?${auth}&limit=5`, result => {
33 | const characters = result.data.results;
34 | this.setState({ characters });
35 | });
36 | }
37 |
38 | CharacterSearch(term) {
39 | $.getJSON(`${API_URL}/characters?${auth}&limit=5&nameStartsWith=${term}`, result => {
40 | const characters = result.data.results;
41 | this.setState({ characters });
42 | });
43 | }
44 |
45 | handleCharacterSelect = character => {
46 | this.setState({ selectedCharacter: character });
47 | };
48 |
49 | render() {
50 | if (!this.state.characters) return Loading...
;
51 | return (
52 |
53 |
54 |
58 |
59 |
60 | );
61 | }
62 | }
63 |
64 | export default App;
65 |
--------------------------------------------------------------------------------
/src/components/character-list-item.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CharacterListItem = props => {
4 | const { character } = props;
5 | return (
6 | props.onCharacterSelect(character)}>
7 |
8 |
9 |

13 |
14 |
15 |
16 |
17 |
{character.name}
18 | {character.description &&
}
19 | {character.description && character.description.length > 50
20 | ? character.description.substr(0, 50) + '...'
21 | : character.description}
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default CharacterListItem;
31 |
--------------------------------------------------------------------------------
/src/components/character-list.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import _ from 'lodash';
3 |
4 | import CharacterListItem from './character-list-item';
5 |
6 | const CharacterList = props => {
7 | return (
8 |
9 | {_.map(props.characters, character => (
10 |
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default CharacterList;
21 |
--------------------------------------------------------------------------------
/src/components/details.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import _ from 'lodash';
3 |
4 | const Detail = props => {
5 | const { character } = props;
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | {character.name}
13 |
14 |
15 |
16 |
17 |

21 |
22 |
23 |
{character.description}
24 |
Comics({character.series.available})
25 |
26 | {_.map(character.comics.items, comic => (
27 | - {comic.name}
28 | ))}
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Detail;
38 |
--------------------------------------------------------------------------------
/src/components/search-bar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class SearchBar extends Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = { term: '' };
7 | }
8 |
9 | onInputChange(term) {
10 | this.setState({ term });
11 | }
12 |
13 | handleKeyPress = event => {
14 | if (event.key === 'Enter') {
15 | this.props.onSearchButtonClick(this.state.term);
16 | }
17 | };
18 |
19 | render() {
20 | return (
21 |
22 |
23 |
24 | this.onInputChange(event.target.value)}
28 | onKeyPress={this.handleKeyPress}
29 | />
30 |
31 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default SearchBar;
46 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { createStore, applyMiddleware } from 'redux';
4 | import { Provider } from 'react-redux';
5 |
6 | import App from './components/App';
7 | import reducers from './reducers';
8 |
9 | import './style/style.css';
10 | import 'bootstrap/dist/css/bootstrap.css';
11 |
12 | const createStoreWithMiddleware = applyMiddleware()(createStore);
13 | const store = createStoreWithMiddleware(
14 | reducers,
15 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
16 | );
17 |
18 | const render = Component =>
19 | ReactDOM.render(
20 |
21 |
22 | ,
23 | document.getElementById('root'),
24 | );
25 |
26 | if (process.env.NODE_ENV === 'development') {
27 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
28 | }
29 |
30 | if (process.env.NODE_ENV === 'development' && module.hot) {
31 | module.hot.accept('./components/App', () => {
32 | const NextApp = require('./components/App').default;
33 | render(NextApp);
34 | });
35 | }
36 |
37 | render(App);
38 |
--------------------------------------------------------------------------------
/src/reducers/first_reducer.js:
--------------------------------------------------------------------------------
1 | export default function(state = {}, action) {
2 | switch (action.type) {
3 | case 'FIRST_ACTION':
4 | return state;
5 | default:
6 | return state;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import firstReducer from './first_reducer';
3 |
4 | const rootReducer = combineReducers({
5 | first: firstReducer,
6 | });
7 |
8 | export default rootReducer;
9 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
17 | );
18 |
19 | export default function register() {
20 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
21 | // The URL constructor is available in all browsers that support SW.
22 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
23 | if (publicUrl.origin !== window.location.origin) {
24 | // Our service worker won't work if PUBLIC_URL is on a different origin
25 | // from what our page is served on. This might happen if a CDN is used to
26 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
27 | return;
28 | }
29 |
30 | window.addEventListener('load', () => {
31 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
32 |
33 | if (!isLocalhost) {
34 | // Is not local host. Just register service worker
35 | registerValidSW(swUrl);
36 | } else {
37 | // This is running on localhost. Lets check if a service worker still exists or not.
38 | checkValidServiceWorker(swUrl);
39 | }
40 | });
41 | }
42 | }
43 |
44 | function registerValidSW(swUrl) {
45 | navigator.serviceWorker
46 | .register(swUrl)
47 | .then(registration => {
48 | registration.onupdatefound = () => {
49 | const installingWorker = registration.installing;
50 | installingWorker.onstatechange = () => {
51 | if (installingWorker.state === 'installed') {
52 | if (navigator.serviceWorker.controller) {
53 | // At this point, the old content will have been purged and
54 | // the fresh content will have been added to the cache.
55 | // It's the perfect time to display a "New content is
56 | // available; please refresh." message in your web app.
57 | console.log('New content is available; please refresh.');
58 | } else {
59 | // At this point, everything has been precached.
60 | // It's the perfect time to display a
61 | // "Content is cached for offline use." message.
62 | console.log('Content is cached for offline use.');
63 | }
64 | }
65 | };
66 | };
67 | })
68 | .catch(error => {
69 | console.error('Error during service worker registration:', error);
70 | });
71 | }
72 |
73 | function checkValidServiceWorker(swUrl) {
74 | // Check if the service worker can be found. If it can't reload the page.
75 | fetch(swUrl)
76 | .then(response => {
77 | // Ensure service worker exists, and that we really are getting a JS file.
78 | if (
79 | response.status === 404 ||
80 | response.headers.get('content-type').indexOf('javascript') === -1
81 | ) {
82 | // No service worker found. Probably a different app. Reload the page.
83 | navigator.serviceWorker.ready.then(registration => {
84 | registration.unregister().then(() => {
85 | window.location.reload();
86 | });
87 | });
88 | } else {
89 | // Service worker found. Proceed as normal.
90 | registerValidSW(swUrl);
91 | }
92 | })
93 | .catch(() => {
94 | console.log('No internet connection found. App is running in offline mode.');
95 | });
96 | }
97 |
98 | export function unregister() {
99 | if ('serviceWorker' in navigator) {
100 | navigator.serviceWorker.ready.then(registration => {
101 | registration.unregister();
102 | });
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/style/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: white;
3 | }
4 |
5 | .search-bar {
6 | margin: 20px;
7 | text-align: center;
8 | }
9 |
10 | .search-bar input {
11 | width: 75%;
12 | }
13 |
14 | .details {
15 | margin-top: 10px;
16 | padding: 10px;
17 | border: 1px solid #ddd;
18 | border-radius: 4px;
19 | }
20 | .details img {
21 | width: 100%;
22 | border: 1px solid #ccc;
23 | border-radius: 4px;
24 | }
25 |
26 | .details h1 {
27 | text-align: center;
28 | }
29 |
30 | .list-item {
31 | cursor: pointer;
32 | margin-top: 10px;
33 | padding: 10px;
34 | border: 1px solid #ddd;
35 | border-radius: 4px;
36 | }
37 |
38 | .list-item:hover {
39 | background-color: #fefefe;
40 | box-shadow: 0 19px 38px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
41 | transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
42 | }
43 |
44 | .list-item img {
45 | border: 1px solid #ddd;
46 | box-shadow: 0 14px 28px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.08);
47 | border-radius: 50%;
48 | }
49 |
--------------------------------------------------------------------------------