├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── App.test.js
├── actions
├── games.js
├── movies.js
└── search.js
├── assets
├── global.scss
├── images
│ ├── arrow-left.svg
│ ├── chevrons-up.svg
│ ├── close.svg
│ ├── logo.png
│ ├── pause-circle.svg
│ ├── play.svg
│ ├── search.svg
│ └── volume-x.svg
└── scss
│ ├── layout
│ └── header.scss
│ ├── product-detail.scss
│ └── product-list.scss
├── components
├── contentLoaderDetail.jsx
├── createGame.jsx
├── createMovie.jsx
├── detail.jsx
├── editGame.jsx
├── editMovie.jsx
├── gameList.jsx
├── movieList.jsx
└── search.jsx
├── index.js
├── layout
└── header.jsx
├── logo.svg
├── pages
├── productCreate.jsx
├── productDetail.jsx
├── productEdit.jsx
└── productPage.jsx
├── reducers
├── games.js
├── movies.js
├── rootReducer.js
└── search.js
├── reportWebVitals.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .eslintcache
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # REACT- REDUX MOVIE APP
2 | Canlı test linki: https://react-redux-advanced-movie-app-duz2oqzak.vercel.app/
3 |
4 | Sevdiğiniz filmlerin veya oyunların ruhunu, onların fon müzikleriyle fazlasıyla hissedebilirsiniz, laptopun sesini çok açmayın :)
5 | Test sayfasından sizde sevdiğiniz film veya oyunları ekleyebilirsiniz.
6 |
7 | Klasik movie app'lerden daha çok, tasarımı özgün, yaratıcı, animasyonlarla süslediğim ve mobili bir app gibi olan bu proje, kod anlamıyla da içime sinen hoş bir proje oldu.
8 |
9 | React ve reduxu tam anlamıyla kullanabilmek adına projeye hep yeni özellikler eklemek istedim, özellikle redux tarafına çok yüklenmeye çalıştım.
10 |
11 | ## Build Setup
12 |
13 | ```bash
14 | # install dependencies
15 | $ npm install
16 |
17 | # serve with hot reload at localhost:3000
18 | $ npm start
19 | ```
20 |
21 |
22 |
23 |

24 |
Developer by Şahin ZAYBAK
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-movie-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.9",
7 | "@testing-library/react": "^11.2.3",
8 | "@testing-library/user-event": "^12.6.2",
9 | "axios": "^0.21.1",
10 | "bootstrap": "^4.6.0",
11 | "node-sass": "^4.14.1",
12 | "react": "^17.0.1",
13 | "react-bootstrap": "^1.4.3",
14 | "react-circular-progressbar": "^2.0.3",
15 | "react-content-loader": "^6.0.1",
16 | "react-debounce-input": "^3.2.3",
17 | "react-dom": "^17.0.1",
18 | "react-modal-video": "^1.2.6",
19 | "react-player": "^2.8.2",
20 | "react-redux": "^7.2.2",
21 | "react-router-dom": "^5.2.0",
22 | "react-scripts": "4.0.1",
23 | "react-spotify-player": "^1.0.4",
24 | "react-thunk": "^1.0.0",
25 | "redux": "^4.0.5",
26 | "redux-devtools-extension": "^2.13.8",
27 | "redux-logger": "^3.0.6",
28 | "redux-promise-middleware": "^6.1.2",
29 | "redux-thunk": "^2.3.0",
30 | "web-vitals": "^0.2.4"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "CI= react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject"
37 | },
38 | "eslintConfig": {
39 | "extends": [
40 | "react-app",
41 | "react-app/jest"
42 | ]
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahinzaybak/react-redux-hooks-advanced-movie-app/7db43d7bca5a81c09a39f50d69fad9c69ed43358/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahinzaybak/react-redux-hooks-advanced-movie-app/7db43d7bca5a81c09a39f50d69fad9c69ed43358/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahinzaybak/react-redux-hooks-advanced-movie-app/7db43d7bca5a81c09a39f50d69fad9c69ed43358/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import {Route} from 'react-router-dom';
2 | import productPage from './pages/productPage.jsx'
3 | import productDetail from './pages/productDetail.jsx'
4 | import productCreate from './pages/productCreate.jsx'
5 | import productEdit from './pages/productEdit.jsx'
6 |
7 | import 'bootstrap/dist/css/bootstrap.css';
8 | import './assets/global.scss'
9 | import './assets/scss/product-list.scss'
10 | import './assets/scss/product-detail.scss'
11 |
12 | function App() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/actions/games.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | const BASE_URL = process.env.REACT_APP_API_URL
3 | export function fetchGames() {
4 | return async dispatch => {
5 | await axios.get(`${BASE_URL}/games`).then(value => {
6 | dispatch({
7 | type: "FETCH_GAMES",
8 | payload: value.data,
9 | });
10 | });
11 | };
12 | }
13 |
14 | export function detail(platform, slug) {
15 | return async dispatch => {
16 | await axios.get(`${BASE_URL}/${platform}?slug=${slug}`).then(value => {
17 | dispatch({
18 | type: "SET_DETAIL",
19 | payload: value.data[0],
20 | });
21 | });
22 | };
23 | }
24 |
25 | export function saveGames(newGameInfo) {
26 | return async dispatch => {
27 | await axios.post(`${BASE_URL}/games`, {
28 | name: newGameInfo._name,
29 | title: newGameInfo._title,
30 | category: newGameInfo._category,
31 | slug: newGameInfo._slug,
32 | poster: newGameInfo._poster,
33 | music: newGameInfo._music,
34 | company: newGameInfo._company,
35 | trailer: newGameInfo._trailer,
36 | time: newGameInfo._time,
37 | year: newGameInfo._year,
38 | point: newGameInfo._point,
39 | platform: "games"
40 | }).then(value => {
41 | dispatch({
42 | type: "NEW_GAME_RESULT",
43 | payload: value.statusText
44 | });
45 | })
46 | }
47 | }
48 |
49 | export function editGames(gameInfo) {
50 | return async dispatch => {
51 | await axios.put(`${BASE_URL}/${gameInfo._platform}/${gameInfo._id}`, {
52 | name: gameInfo._name,
53 | title: gameInfo._title,
54 | category: gameInfo._category,
55 | slug: gameInfo._slug,
56 | poster: gameInfo._poster,
57 | backdrop: gameInfo._backdrop,
58 | company: gameInfo._company,
59 | trailer: gameInfo._trailer,
60 | time: gameInfo._time,
61 | year: gameInfo._year,
62 | point: gameInfo._point,
63 | platform: "games"
64 | }).then(value => {
65 | dispatch({
66 | type: "EDIT_RESULT",
67 | payload: value
68 | });
69 | })
70 | }
71 | }
72 |
73 | export function clearDetail() {
74 | return dispatch => {
75 | dispatch({
76 | type: "CLEAR_DETAIL",
77 | payload: true
78 | });
79 | }
80 | }
--------------------------------------------------------------------------------
/src/actions/movies.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | const BASE_URL = process.env.REACT_APP_API_URL
3 | export function fetchMovies() {
4 | return async dispatch => {
5 | await axios.get(`${BASE_URL}/movies`).then(value => {
6 | dispatch({
7 | type: "FETCH_MOVIES",
8 | payload: value.data,
9 | });
10 | });
11 | };
12 | }
13 |
14 | export function saveMovie(newMovieInfo) {
15 | return async dispatch => {
16 | await axios.post(`${BASE_URL}/movies`, {
17 | name: newMovieInfo._name,
18 | title: newMovieInfo._title,
19 | category: newMovieInfo._category,
20 | slug: newMovieInfo._slug,
21 | poster: newMovieInfo._poster,
22 | music: newMovieInfo._music,
23 | director: newMovieInfo._director,
24 | writer: newMovieInfo._writer,
25 | trailer: newMovieInfo._trailer,
26 | time: newMovieInfo._time,
27 | year: newMovieInfo._year,
28 | point: newMovieInfo._point,
29 | platform: "movies"
30 | }).then(value => {
31 | dispatch({
32 | type: "NEW_MOVIE_RESULT",
33 | payload: value.statusText
34 | });
35 | })
36 | }
37 | }
38 |
39 |
40 | export function editMovie(movieInfo) {
41 | return async dispatch => {
42 | await axios.put(`${BASE_URL}/${movieInfo._platform}/${movieInfo._id}`, {
43 | name: movieInfo._name,
44 | title: movieInfo._title,
45 | category: movieInfo._category,
46 | slug: movieInfo._slug,
47 | poster: movieInfo._poster,
48 | music: movieInfo._music,
49 | director: movieInfo._director,
50 | writer: movieInfo._writer,
51 | trailer: movieInfo._trailer,
52 | time: movieInfo._time,
53 | year: movieInfo._year,
54 | point: movieInfo._point,
55 | platform: "movies"
56 | }).then(value => {
57 | dispatch({
58 | type: "EDIT_RESULT",
59 | payload: value
60 | });
61 | })
62 | }
63 | }
--------------------------------------------------------------------------------
/src/actions/search.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | const BASE_URL = process.env.REACT_APP_API_URL
3 | export function searchGames(searchText) {
4 | return async dispatch => {
5 | await axios.get(`${BASE_URL}/games?name=${searchText}`).then(value => {
6 | dispatch({
7 | type: "SEARCH_GAME_RESULT",
8 | payload: value.data
9 | });
10 | })
11 | }
12 | }
13 |
14 | export function searchMovies(searchText) {
15 | return async dispatch => {
16 | await axios.get(`${BASE_URL}/movies?name=${searchText}`).then(value => {
17 | dispatch({
18 | type: "SEARCH_MOVIE_RESULT",
19 | payload: value.data
20 | });
21 | })
22 | }
23 | }
24 |
25 | export function searchActiveOverlay() {
26 | return dispatch => {
27 | dispatch({
28 | type: "ACTIVE_SEARCH_OVERLAY",
29 | payload: "true"
30 | });
31 | }
32 | }
33 |
34 | export function searchDisableOverlay() {
35 | return dispatch => {
36 | dispatch({
37 | type: "DISABLED_SEARCH_OVERLAY",
38 | payload: "false"
39 | });
40 | }
41 | }
42 |
43 | export function searchListClear() {
44 | return dispatch => {
45 | dispatch({
46 | type: "SEARCH_LIST_CLEAR",
47 | });
48 | }
49 | }
--------------------------------------------------------------------------------
/src/assets/global.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap');
2 | @import url('https://fonts.googleapis.com/css2?family=Knewave&display=swap');
3 | @import url('https://fonts.googleapis.com/css2?family=Hind+Siliguri:wght@400;500&display=swap');
4 |
5 | * {
6 |
7 | &,
8 | &:before,
9 | &:after {
10 | box-sizing: border-box;
11 | }
12 | }
13 |
14 | *,
15 | *:focus {
16 | outline: none;
17 | }
18 |
19 | html {
20 | box-sizing: border-box;
21 | text-rendering: optimizeLegibility;
22 | }
23 |
24 | body {
25 | overflow-x: hidden !important;
26 | -webkit-font-smoothing: antialiased;
27 | -moz-osx-font-smoothing: grayscale;
28 | padding: 0;
29 | margin: 0;
30 | font-family: 'Montserrat';
31 | font-weight: 500;
32 | letter-spacing: 0.2px;
33 |
34 | }
35 |
36 |
37 | blockquote,
38 | dl,
39 | dd,
40 | h1,
41 | h2,
42 | h3,
43 | h4,
44 | h5,
45 | h6,
46 | figure,
47 | p,
48 | pre,
49 | fieldset,
50 | ul,
51 | ol,
52 | menu,
53 | form {
54 | margin: 0;
55 | }
56 |
57 | button,
58 | fieldset,
59 | iframe {
60 | border: 0;
61 | }
62 |
63 | fieldset,
64 | ul,
65 | ol,
66 | button,
67 | menu {
68 | padding: 0;
69 | }
70 |
71 | ul {
72 | list-style: none;
73 | }
74 |
75 | textarea {
76 | resize: none;
77 | }
78 |
79 | table {
80 | width: 100%;
81 | border-collapse: collapse;
82 | border-spacing: 0;
83 | }
84 |
85 | td {
86 | padding: 0;
87 | }
88 |
89 | a,
90 | a:hover,
91 | a:focus,
92 | a:active {
93 | text-decoration: none;
94 | color: inherit;
95 | }
96 |
97 | a,
98 | a:focus,
99 | button,
100 | button:focus {
101 | outline: none !important;
102 | text-decoration: none;
103 | }
104 |
105 | a,
106 | button {
107 | user-select: none;
108 | transition: all .3s ease-in-out;
109 | }
110 |
111 | button {
112 | cursor: pointer;
113 | background: none;
114 | box-shadow: none;
115 | border: none;
116 | }
117 |
118 | img {
119 | max-width: 100%;
120 | }
121 |
122 | section {
123 | padding: 80px 0;
124 | }
125 |
126 | label {
127 | margin-bottom: 0;
128 | }
129 |
130 |
131 | .custom-container {
132 | position: relative;
133 | max-width: 1200px;
134 | width: 95%;
135 | margin: 0 auto;
136 | z-index: 99;
137 | &.product{
138 | @media (max-width: 600px) {
139 | overflow: hidden;
140 | }
141 | }
142 | }
143 |
144 | .cursor-pointer {
145 | cursor: pointer;
146 | }
147 |
148 | .custom-switch {
149 | @media (max-width: 600px) {
150 | display: none;
151 | }
152 | }
153 |
154 | .dark {
155 | background-color: black;
156 | color: white;
157 |
158 | .header {
159 | background-color: black;
160 | }
161 |
162 | .movie-item__name {
163 | color: white;
164 | }
165 |
166 | .movie-create__form {
167 | input[type=text] {
168 | border: 1px solid #fdfdfd26;
169 | background-color: transparent;
170 | }
171 | }
172 | .search-result{
173 | background-color: black !important;
174 | border-color: #131212 !important;
175 | }
176 | }
177 |
178 | .back-info {
179 | position: absolute;
180 | right: 0;
181 | left: 0;
182 | background-color: white;
183 | border-radius: 50px;
184 | width: 35px;
185 | height: 35px;
186 | display: flex;
187 | align-items: center;
188 | justify-content: center;
189 | margin: 0 auto;
190 | transform: scale(1.2);
191 | bottom: 46px;
192 | opacity: 0;
193 | transform: translateY(50px);
194 | transition: all .4s ease-in;
195 | cursor: pointer;
196 | z-index: 9;
197 | }
198 |
199 | .activeTrailer {
200 | .movie-detail__wrp {
201 | opacity: 0;
202 | transform: translateY(200px);
203 | padding-top: 0;
204 | }
205 |
206 | .back-info {
207 | opacity: 0.7;
208 | transform: none;
209 | }
210 |
211 | .movie-detail__backdrop {
212 | @media (max-width: 600px) {
213 | z-index: 1;
214 | }
215 | &:after {
216 | background-color: transparent;
217 | @media (max-width: 600px) {
218 | display: none;
219 | }
220 | }
221 |
222 | }
223 | }
224 |
225 | .status-message{
226 | position: absolute;
227 | bottom: 5px;
228 | right: 0;
229 | left: 0;
230 | color: #95cc26;
231 | font-weight: 600;
232 | font-size: 17px;
233 | width: max-content;
234 | margin: 0 auto;
235 | &.yellow{
236 | color:#ea8835;
237 | }
238 | }
239 |
240 | .load-skeleton-detail{
241 | position: absolute;
242 | left: -4%;
243 | transform: translateY(-40%);
244 | @media (max-width: 650px) {
245 | top: -68px;
246 | position: absolute;
247 | left: 0;
248 | transform: none;
249 | overflow: hidden;
250 | }
251 | }
252 |
253 | .search-overlay{
254 | position: fixed;
255 | width: 100%;
256 | height: 100vh;
257 | background-color: #000000e0;
258 | top: 80px;
259 | z-index: 9999;
260 | opacity: 0;
261 | transition: all .2s 0.2s ease;
262 | pointer-events: none;
263 | &.active{
264 | opacity: 1;
265 | pointer-events: all;
266 | }
267 | }
--------------------------------------------------------------------------------
/src/assets/images/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/chevrons-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sahinzaybak/react-redux-hooks-advanced-movie-app/7db43d7bca5a81c09a39f50d69fad9c69ed43358/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/pause-circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/volume-x.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/scss/layout/header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | height: 80px;
4 | background-color: #151515;
5 | color: white;
6 | border-bottom: 1px solid transparent;
7 | opacity: 1;
8 | z-index: 99999;
9 |
10 | &-logo {
11 | width: 110px;
12 | }
13 |
14 | &-menu {
15 | &__link {
16 | color: white;
17 | margin-right: 30px;
18 | }
19 |
20 | &__search {
21 | position: relative;
22 | width: 480px;
23 | display: flex;
24 | align-items: center;
25 |
26 | .search {
27 | &-icon {
28 | position: absolute;
29 | opacity: 0.3;
30 | }
31 |
32 | &-item {
33 | input {
34 | width: 100%;
35 | border: 1px solid #ffffff17;
36 | border-radius: 6px;
37 | padding: 9px;
38 | text-indent: 60px;
39 | background-color: transparent;
40 | color: white;
41 | }
42 | &__platform{
43 | font-size: 10px;
44 | background-color: #95cc26;
45 | padding: 2px 9px;
46 | border-radius: 6px;
47 | &.red{
48 | background-color: #ea8835;
49 | }
50 | }
51 | .spinner-border{
52 | left: 25px;
53 | width: 1.4em;
54 | height: 1.4em;
55 | }
56 | }
57 |
58 | &-result {
59 | position: absolute;
60 | width: 100%;
61 | background-color: #161616;
62 | z-index: 99999;
63 | padding: 23px;
64 | opacity: 0;
65 | pointer-events: none;
66 | transition: all .3s;
67 | border: 1px solid #2a2a2a;
68 | margin-top: -3px;
69 | border-bottom-left-radius: 6px;
70 | border-bottom-right-radius: 6px;
71 |
72 | &__name {
73 | font-size: 17px;
74 | opacity: 0.9;
75 | margin-bottom: 3px;
76 | }
77 |
78 | &__cat {
79 | font-size: 13px;
80 | opacity: 0.6;
81 | }
82 |
83 | &__item {
84 | display: flex;
85 | align-items: flex-start;
86 | padding-bottom: 10px;
87 | margin-bottom: 10px;
88 | border-bottom: 1px solid #f5f5f50f;
89 | justify-content: space-between;
90 | &:last-child{
91 | margin-bottom: 0;
92 | padding-bottom: 0;
93 | border-bottom: 0;
94 | }
95 |
96 | img {
97 | width: 37px;
98 | height: 47px;
99 | border-radius: 6px;
100 | margin-right: 10px;
101 | }
102 | }
103 |
104 | &.active {
105 | pointer-events: all;
106 | opacity: 1;
107 | }
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
114 | .header-nobg {
115 | overflow: hidden;
116 | height: 100vh;
117 | background-color: black;
118 | .header-menu{
119 | opacity: 0;
120 | }
121 |
122 | @media (max-width: 600px) {
123 | overflow: auto;
124 | }
125 | .search{
126 | opacity: 0;
127 | }
128 |
129 | &.overflow-hidden {
130 | .header {
131 | opacity: 0;
132 | @media (max-width: 600px) {
133 | display: none;
134 | }
135 | }
136 |
137 | .movie-detail__info {
138 | @media (max-width: 600px) {
139 | display: none;
140 | }
141 | }
142 | }
143 |
144 | .header {
145 | background-color: transparent;
146 | @media (max-width: 600px) {
147 | display: none;
148 | }
149 | }
150 | }
--------------------------------------------------------------------------------
/src/assets/scss/product-detail.scss:
--------------------------------------------------------------------------------
1 | .movie {
2 | &-detail {
3 |
4 | &__wrp {
5 | position: absolute;
6 | width: 100%;
7 | top: 52%;
8 | transform: translateY(-50%);
9 | transition: all 0.4s ease-in;
10 | @media (max-width: 768px) {
11 | top:0;
12 | transform: none;
13 | width: 100%;
14 | overflow-x: hidden;
15 | background-color: black;
16 | height: -webkit-fill-available;
17 | }
18 |
19 | &.active-edit {
20 | padding-top: 0;
21 | }
22 | }
23 |
24 | &__content {
25 | transition: all .5s ease-in-out;
26 |
27 | &.active {
28 | opacity: 0;
29 | transform: translateY(200px);
30 | padding-top: 0;
31 | }
32 | }
33 |
34 | &__backdrop {
35 | position: absolute;
36 | width: 100%;
37 | height: 100vh;
38 | top: 0;
39 | z-index: -1;
40 |
41 | div {
42 | width: 100% !important;
43 | height: 100% !important;
44 | }
45 |
46 | img {
47 | width: 100%;
48 | height: 100%;
49 | object-fit: cover;
50 | }
51 |
52 | &:after {
53 | content: '';
54 | position: absolute;
55 | width: 100%;
56 | height: 100%;
57 | transition: all .4s ease-in;
58 | background-color: #0a0a0ad1;
59 | top: 0;
60 | left: 0;
61 | }
62 |
63 | &.active {
64 | &:after {
65 | background-color: #0a0a0aed;
66 | }
67 | }
68 | }
69 |
70 | &__poster {
71 | position: relative;
72 | height: 414px;
73 | background-color: #b1b1b1;
74 | border-radius: 12px;
75 | img {
76 | box-shadow: 0 2px 8px #0000001a;
77 | border-radius: 12px;
78 | width: 100%;
79 | height: 100%;
80 | object-fit: cover;
81 | transition: all .8s ease;
82 | transform: scale(1);
83 | @media (max-width: 650px) {
84 | display: none;
85 | }
86 | }
87 | @media (max-width: 650px) {
88 | margin-top: 10px;
89 | }
90 | }
91 |
92 | &__title {
93 | font-weight: 700;
94 |
95 | @media (max-width: 768px) {
96 | font-size: 26px;
97 | margin-right: 18px;
98 | }
99 |
100 | span {
101 | font-weight: 100;
102 | color: #bdbdbd;
103 | }
104 | }
105 |
106 | &__top {
107 | position: relative;
108 | color: #bdbdbd;
109 | font-size: 15px;
110 | padding-left: 12px;
111 |
112 | &:before {
113 | content: '';
114 | position: absolute;
115 | width: 5px;
116 | height: 5px;
117 | background-color: white;
118 | border-radius: 50px;
119 | left: 0;
120 | top: 9px;
121 | }
122 | }
123 |
124 | .CircularProgressbar {
125 | width: 60px;
126 |
127 | &-text {
128 | font-weight: 600;
129 | }
130 |
131 | &-path {
132 | stroke: #21d07a !important;
133 | }
134 | }
135 |
136 | &__icon {
137 | background-color: gainsboro;
138 | border-radius: 50px;
139 | padding: 3px;
140 | width: 40px;
141 | height: 40px;
142 | display: flex;
143 | align-items: center;
144 | justify-content: center;
145 | transition: all .4s;
146 | opacity: 0.4;
147 |
148 | img {
149 | width: 16px;
150 | }
151 |
152 | &:hover {
153 | opacity: 1;
154 | }
155 |
156 | &.opacity {
157 | opacity: 1;
158 | }
159 | }
160 |
161 | &__summary {
162 | font-size: 17px;
163 | color: #e4e4e4;
164 | line-height: 1.6;
165 | }
166 |
167 | .modal-video-body {
168 | max-width: 1200px;
169 | }
170 |
171 | &__spotify {
172 | position: absolute;
173 | width: 100%;
174 | height: 100%;
175 | bottom: 0;
176 | opacity: 0;
177 | border-bottom-left-radius: 12px;
178 | border-bottom-right-radius: 12px;
179 | right: 0;
180 | object-fit: cover;
181 | transform: scale(0);
182 | transition: all .6s ease-in-out;
183 | @media (max-width: 650px) {
184 | transform: none;
185 | opacity: 1;
186 | }
187 |
188 | div {
189 | width: 100% !important;
190 | height: 101% !important;
191 | }
192 |
193 | iframe {
194 | width: 100%;
195 | height: 100%;
196 | border-radius: 12px;
197 | }
198 | }
199 |
200 | &__edit {
201 | padding: 40px;
202 | top: 50%;
203 | border-radius: 9px;
204 | opacity: 0;
205 | position: absolute;
206 | width: 100%;
207 | transition: all 0.6s ease-in-out;
208 | transform: translateY(-20%);
209 |
210 | &.active {
211 | opacity: 1;
212 | transform: translateY(-50%);
213 | }
214 |
215 | .back-icon {
216 | position: absolute;
217 | right: 3%;
218 | transform: scale(1.8);
219 | z-index: 999;
220 | cursor: pointer;
221 | }
222 |
223 | @media (max-width: 650px) {
224 | padding: 0;
225 | top: 50px;
226 | transform: none !important;
227 | }
228 | }
229 |
230 | &__back{
231 | position: absolute;
232 | top: -50px;
233 | cursor: pointer;
234 | img{
235 | transform: scale(1.5);
236 | }
237 | @media (max-width: 650px) {
238 | top: 20px;
239 | z-index: 99;
240 | left: 6%;
241 | background-color: #79747440;
242 | padding: 5px 6px;
243 | border-radius: 50px;
244 | }
245 | }
246 | }
247 | }
248 |
249 |
--------------------------------------------------------------------------------
/src/assets/scss/product-list.scss:
--------------------------------------------------------------------------------
1 | .movie {
2 | &-create {
3 | &__item {
4 | margin-bottom: 20px;
5 |
6 | input,
7 | textarea {
8 | width: 100%;
9 | padding: 9px 14px;
10 | border-radius: 5px;
11 | border: 0;
12 | border: 1px solid #313131;
13 | background-color: transparent;
14 | color: #c7c7c7;
15 | font-size: 15px;
16 | font-weight: 100;
17 |
18 | &:nth-child(3n) {
19 | margin-right: 0;
20 | }
21 | }
22 | }
23 |
24 | &__button {
25 | border: 0;
26 | padding: 8px 12px;
27 | border-radius: 5px;
28 | background-color: #94cc26;
29 | color: white;
30 | font-size: 14px;
31 | font-weight: 600;
32 | width: 100px;
33 | margin-left: auto;
34 | transition: all .3s;
35 | &:hover{
36 | background-color: #7dad1f;
37 | color:white;
38 | }
39 |
40 | &.yellow {
41 | background-color: #ea8835;
42 | &:hover{
43 | background-color: #d0762a;
44 | color:white;
45 | }
46 | }
47 | }
48 |
49 | &.edit {
50 | .movie-create__item {
51 | input {
52 | border: 1px solid #3e3e3e;
53 | background-color: #ffffff14;
54 | color: white;
55 | }
56 | }
57 | }
58 |
59 | &__form {
60 | p {
61 | font-size: 15px;
62 | color: #6f824a;
63 | margin-bottom: 5px;
64 | font-weight: 400;
65 | @media (max-width: 650px) {
66 | font-size: 10px;
67 | }
68 | }
69 |
70 | &.movie {
71 | p {
72 | color: #968136;
73 | }
74 | }
75 | }
76 |
77 | &__backdrop {
78 | position: absolute;
79 | height: 100vh;
80 |
81 | img {
82 | width: 100%;
83 | }
84 | }
85 |
86 | &.page {
87 | .movie-create__form{
88 | background-color: #080808ba;
89 | padding: 20px 35px;
90 | border-radius: 9px;
91 | @media (max-width: 650px) {
92 | padding: 0;
93 | background-color: transparent;
94 | }
95 | }
96 | .movie-create__item {
97 | input,
98 | textarea {
99 | border: 1px solid #585858;
100 | }
101 | }
102 | }
103 | }
104 |
105 | &-add {
106 | position: relative;
107 | padding: 6px 24px;
108 | border-radius: 9px;
109 | color: white;
110 | text-align: center;
111 | z-index: 999;
112 | transition: all .3s;
113 | cursor: pointer;
114 |
115 | @media (max-width: 768px) {
116 | font-size: 12px;
117 | max-width: 146px;
118 | flex-shrink: 0;
119 | }
120 |
121 | &.green {
122 | background-color: #94cc26;
123 | &:hover{
124 | background-color: #7dad1f;
125 | color:white;
126 | }
127 | }
128 |
129 | &.yellow {
130 | background-color: #ea8835;
131 | &:hover{
132 | background-color: #d0762a;
133 | color:white;
134 | }
135 | }
136 | }
137 |
138 | &-item {
139 | &__img {
140 | width: 100%;
141 | height: 260px;
142 | object-fit: cover;
143 | box-shadow: 0 2px 8px #0000001a;
144 | border-radius: 12px;
145 | }
146 |
147 | &__name {
148 | color: black;
149 | font-size: 16px;
150 | font-weight: 700;
151 | transition: all 0.4s ease-in-out;
152 | }
153 | }
154 |
155 | .CircularProgressbar {
156 | width: 40px;
157 | margin-top: -30px;
158 |
159 | &-text {
160 | font-weight: 600;
161 | }
162 |
163 | &-path {
164 | stroke: #21d07a;
165 | }
166 | }
167 | }
--------------------------------------------------------------------------------
/src/components/contentLoaderDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import ContentLoader from 'react-content-loader'
3 |
4 | class contentLoaderDetail extends PureComponent {
5 | render() {
6 | return (
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 | export default contentLoaderDetail;
35 |
--------------------------------------------------------------------------------
/src/components/createGame.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
3 |
4 | //Actions
5 | import {saveGames} from '../actions/games'
6 |
7 | //Class component
8 | class CreateGame extends Component {
9 | render(){
10 | return (
11 |
12 |
13 |

14 |
15 |
83 |
84 | )
85 | }
86 |
87 | state = {
88 | name:'',
89 | title:'',
90 | category:'',
91 | slug:'',
92 | poster:'',
93 | music:'',
94 | company:'',
95 | trailer:'',
96 | time:'',
97 | year:'',
98 | point:'',
99 | message:true
100 | }
101 |
102 | componentDidMount(){
103 | this.setState({message:false})
104 | }
105 |
106 | handleTextChanged = (e) => { //bunu yazmazsak inputa yazı giremeyiz..
107 | this.setState({ [e.target.name]: e.target.value });
108 | }
109 |
110 | handleSubmit = (e) => {
111 | e.preventDefault();
112 | this.props.saveGames({
113 | _name:this.state.name,
114 | _title:this.state.title,
115 | _category:this.state.category,
116 | _slug:this.state.slug,
117 | _poster:this.state.poster,
118 | _music:this.state.music,
119 | _company:this.state.company,
120 | _trailer:this.state.trailer,
121 | _time:this.state.time,
122 | _year:this.state.year,
123 | _point:this.state.point
124 | });
125 | this.setState({message:true})
126 | setTimeout(() => {
127 | this.setState({message:false})
128 | }, 2000);
129 | }
130 |
131 | }
132 |
133 | const mapStateToProps = (state) => {
134 | return {
135 | createResult: state.games.createGameResult
136 | }
137 | }
138 |
139 | const mapDispatchToProps = {
140 | saveGames,
141 | }
142 |
143 | export default connect(mapStateToProps,mapDispatchToProps) (CreateGame);
144 |
--------------------------------------------------------------------------------
/src/components/createMovie.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
3 |
4 | //Actions
5 | import {saveMovie} from '../actions/movies'
6 |
7 | //Class component
8 | class CreateMovie extends Component {
9 | render(){
10 | return (
11 |
12 |
13 |

14 |
15 |
16 |
Film Ekle
17 |
87 |
88 |
89 | )
90 | }
91 |
92 | state = {
93 | name:'',
94 | title:'',
95 | category:'',
96 | slug:'',
97 | poster:'',
98 | director: '',
99 | writer: '',
100 | trailer:'',
101 | music:'',
102 | time:'',
103 | year:'',
104 | point:'',
105 | message:true
106 | }
107 |
108 | componentDidMount(){
109 | this.setState({message:false})
110 | }
111 |
112 | handleTextChanged = (e) => { //bunu yazmazsak inputa yazı giremeyiz..
113 | this.setState({ [e.target.name]: e.target.value });
114 | }
115 |
116 | handleSubmit = (e) => {
117 | e.preventDefault();
118 | this.props.saveMovie({
119 | _name:this.state.name,
120 | _title:this.state.title,
121 | _category:this.state.category,
122 | _slug:this.state.slug,
123 | _poster:this.state.poster,
124 | _music:this.state.music,
125 | _director:this.state.director,
126 | _writer:this.state.writer,
127 | _trailer:this.state.trailer,
128 | _time:this.state.time,
129 | _year:this.state.year,
130 | _point:this.state.point,
131 | });
132 | this.setState({message:true})
133 | setTimeout(() => {
134 | this.setState({message:false})
135 | }, 2000);
136 | }
137 | }
138 |
139 | const mapStateToProps = (state) => {
140 | return {
141 | createResult: state.movies.createMovieResult
142 | }
143 | }
144 |
145 | const mapDispatchToProps = {
146 | saveMovie,
147 | }
148 |
149 | export default connect(mapStateToProps,mapDispatchToProps) (CreateMovie);
150 |
--------------------------------------------------------------------------------
/src/components/detail.jsx:
--------------------------------------------------------------------------------
1 | import React,{useState} from 'react'
2 | import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
3 | import 'react-circular-progressbar/dist/styles.css';
4 | import ReactPlayer from 'react-player'
5 | import {Link} from 'react-router-dom';
6 |
7 | //Images
8 | import muted from '../assets/images/volume-x.svg';
9 | import play from '../assets/images/play.svg';
10 | import up from '../assets/images/chevrons-up.svg';
11 | import close from '../assets/images/close.svg';
12 | import back from '../assets/images/arrow-left.svg';
13 |
14 | //Component
15 | import EditMovie from '../components/editMovie'
16 | import EditGames from '../components/editGame'
17 | import ContentLoaderDetail from '../components/contentLoaderDetail'
18 |
19 | const MovieDetail = ({ detailInfo }) => { //no props => ({movies})
20 | const volumeTrailer = 1;
21 | const volumePosterTrailer = 0.8;
22 |
23 | const [playingPoster, setPlayPoster] = useState(true) //poster trailer
24 | const [mobileVideoVolume, setMobileVideoVolume] = useState(0.8) //poster trailer
25 | const [trailerMuted, setTrailerMuted] = useState(true) //arka plan video ses
26 | const [isTrailer, setTrailer] = useState(false) //trailer izlemek istiyor mu?
27 | const [isMute, setIsMute] = useState(false) //trailerdan çıkınca mute aktif mi?
28 | const [isEdit, setIsEdit] = useState(false) //trailerdan çıkınca mute aktif mi?
29 |
30 |
31 | function toggleMute(){
32 | setPlayPoster(!playingPoster);
33 | if(playingPoster) setIsMute(true) // isMute = true
34 | else setIsMute(false)
35 | }
36 |
37 | function isMutePoster(){
38 | if(isMute) setPlayPoster(false) // playingPoster = false
39 | else setPlayPoster(true)
40 | }
41 |
42 |
43 | //Mobile arka plan trailer için.
44 | function bodyAddClass(){
45 | document.body.classList.add("overflow-hidden");
46 | }
47 |
48 | function bodyRemoveClass(){
49 | document.body.classList.remove("overflow-hidden");
50 | }
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 | {!detailInfo.title &&
60 |
61 | }
62 | {detailInfo.title &&
63 |
64 |
65 |
66 |
67 |

68 |
69 |
70 |

71 | {window.innerWidth > 650 &&
72 |
73 |
74 |
75 | }
76 | {window.innerWidth < 650 &&
77 |
78 |
79 |
80 | }
81 |
82 |
83 |
84 |
85 |
86 |
{detailInfo.name} ({detailInfo.year})
87 | {detailInfo.platform=="games" &&
88 |
{setIsEdit(true); bodyAddClass()}}>Oyunu Düzenle
89 | }
90 | {detailInfo.platform=="movies" &&
91 |
{setIsEdit(true); bodyAddClass()}}>Filmi Düzenle
92 | }
93 |
94 |
95 | {detailInfo.company &&
96 |
{detailInfo.company}
97 | }
98 | {detailInfo.director &&
99 |
{detailInfo.director}
100 | }
101 | {detailInfo.writer &&
102 |
{detailInfo.writer}
103 | }
104 |
{detailInfo.category}
105 |
{detailInfo.time}
106 |
107 |
108 |
115 |
116 |

117 |
118 |
{
119 | setPlayPoster(false);
120 | setTrailer(true);
121 | setTrailerMuted(false);
122 | bodyAddClass();
123 | setMobileVideoVolume(0)
124 | }}>
125 |
126 |

127 |
128 |
Fragmana geç
129 |
130 |
131 |
132 |
Özet
133 |
{detailInfo.title}
134 |
135 |
136 |
137 |
138 |
139 | }
140 |
141 |

{setIsEdit(false); bodyRemoveClass()}}/>
142 | {isEdit && detailInfo.platform == "movies" &&
143 |
144 | }
145 | {isEdit && detailInfo.platform == "games" &&
146 |
147 | }
148 |
149 |
150 |
151 |
152 |
{
153 | setPlayPoster(true);
154 | isMutePoster();
155 | setTrailer(false);
156 | setTrailerMuted(true);
157 | bodyRemoveClass()
158 | setMobileVideoVolume(0.8)
159 | }}>
160 |

161 |
162 |
163 | )
164 | }
165 | export default MovieDetail;
--------------------------------------------------------------------------------
/src/components/editGame.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {connect,} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
4 |
5 |
6 | //actions
7 | import {editGames} from '../actions/games'
8 | //function component
9 | class EditGame extends PureComponent {
10 | render(){
11 | return (
12 |
13 |
14 | {this.props.detailInfos.platform == "games" &&
15 |
Oyun Düzenle
16 | }
17 | {this.props.detailInfos.platform == "movies" &&
18 |
Film Düzenle
19 | }
20 |
{this.props.detailInfos.name}
21 |
83 |
84 |
85 | )
86 | }
87 |
88 | state = {
89 | id: this.props.detailInfos.id,
90 | name:this.props.detailInfos.name,
91 | title:this.props.detailInfos.title,
92 | category:this.props.detailInfos.category,
93 | slug:this.props.detailInfos.slug,
94 | poster:this.props.detailInfos.poster,
95 | music:this.props.detailInfos.music,
96 | company:this.props.detailInfos.company,
97 | trailer:this.props.detailInfos.trailer,
98 | time:this.props.detailInfos.time,
99 | year:this.props.detailInfos.year,
100 | point:this.props.detailInfos.point,
101 | platform: this.props.detailInfos.platform,
102 | message:true
103 | }
104 |
105 | componentDidMount(){
106 | this.setState({message:false})
107 | }
108 |
109 | handleTextChanged = (e) => { //bunu yazmazsak inputa yazı giremeyiz..
110 | this.setState({ [e.target.name]: e.target.value });
111 | }
112 |
113 | handleSubmit = (e) => {
114 | e.preventDefault();
115 | this.props.editGames({
116 | _id:this.state.id,
117 | _name:this.state.name,
118 | _title:this.state.title,
119 | _category:this.state.category,
120 | _slug:this.state.slug,
121 | _poster:this.state.poster,
122 | _music:this.state.music,
123 | _company:this.state.company,
124 | _trailer:this.state.trailer,
125 | _time:this.state.time,
126 | _year:this.state.year,
127 | _point:this.state.point,
128 | _platform: this.state.platform
129 | });
130 | this.setState({message:true})
131 | setTimeout(() => {
132 | this.setState({message:false})
133 | }, 2000);
134 | }
135 | }
136 |
137 | EditGame.propTypes = {
138 | detailInfos: PropTypes.object.isRequired //Function componenet parametre alma!
139 | };
140 |
141 |
142 | const mapStateToProps = (state) => { //state => state.games --> games bana rootReducerdan geliyor..
143 | return {
144 | createResult: state.games.createGameResult
145 | }
146 | }
147 |
148 | const mapDispatchToProps = {
149 | editGames
150 | }
151 |
152 | export default connect(mapStateToProps,mapDispatchToProps) (EditGame);
153 |
--------------------------------------------------------------------------------
/src/components/editMovie.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
4 |
5 | //actions
6 | import {editMovie} from '../actions/movies'
7 | //function component
8 |
9 | class EditMovie extends PureComponent {
10 | render(){
11 | return (
12 |
13 |
14 | {this.props.detailInfos.platform == "games" &&
15 |
Oyun Düzenle
16 | }
17 | {this.props.detailInfos.platform == "movies" &&
18 |
Film Düzenle
19 | }
20 |
{this.props.detailInfos.name}
21 |
81 |
82 |
83 | )
84 | }
85 |
86 | state = { //data
87 | id: this.props.detailInfos.id,
88 | name:this.props.detailInfos.name,
89 | title:this.props.detailInfos.title,
90 | category:this.props.detailInfos.category,
91 | slug:this.props.detailInfos.slug,
92 | poster:this.props.detailInfos.poster,
93 | music:this.props.detailInfos.music,
94 | director: this.props.detailInfos.director,
95 | writer: this.props.detailInfos.writer,
96 | trailer:this.props.detailInfos.trailer,
97 | time:this.props.detailInfos.time,
98 | year:this.props.detailInfos.year,
99 | point:this.props.detailInfos.point,
100 | platform: this.props.detailInfos.platform,
101 | message:true
102 | }
103 |
104 | handleTextChanged = (e) => { //bunu yazmazsak inputa yazı giremeyiz..
105 | this.setState({ [e.target.name]: e.target.value });
106 | }
107 |
108 | componentDidMount(){
109 | this.setState({message:false})
110 | }
111 |
112 | handleSubmit = (e) => {
113 | e.preventDefault();
114 | this.props.editMovie({
115 | _id:this.state.id,
116 | _name:this.state.name,
117 | _title:this.state.title,
118 | _category:this.state.category,
119 | _slug:this.state.slug,
120 | _poster:this.state.poster,
121 | _music:this.state.music,
122 | _director:this.state.director,
123 | _writer:this.state.writer,
124 | _trailer:this.state.trailer,
125 | _time:this.state.time,
126 | _year:this.state.year,
127 | _point:this.state.point,
128 | _platform: this.state.platform
129 | })
130 | this.setState({message:true})
131 | setTimeout(() => {
132 | this.setState({message:false})
133 | }, 2000);
134 | }
135 | }
136 |
137 | EditMovie.propTypes = {
138 | detailInfos: PropTypes.object.isRequired //Function component parametre alma!
139 | };
140 |
141 |
142 | const mapStateToProps = (state) => { //state => state.games --> games bana rootReducerdan geliyor..
143 | return {
144 | createResult: state.games.createGameResult
145 | }
146 | }
147 |
148 | const mapDispatchToProps = {
149 | editMovie
150 | }
151 |
152 | export default connect(mapStateToProps,mapDispatchToProps) (EditMovie);
153 |
--------------------------------------------------------------------------------
/src/components/gameList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types';
3 | import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
4 | import 'react-circular-progressbar/dist/styles.css';
5 | import {Link} from 'react-router-dom';
6 | import ContentLoader from 'react-content-loader'
7 |
8 | const gameList = ({ games }) => { //no props => ({movies})
9 |
10 | return (
11 |
12 |
13 |
OYUNLAR
14 | Oyun Ekle
15 |
16 |
17 | {games.gameList.length == 0 &&
18 |
19 | {[...Array(6)].map((x, i) =>
20 |
21 |
22 |
23 |
24 |
25 |
26 | )}
27 |
28 | }
29 |
30 |
31 | {games.gameList.map(game =>
32 |
33 |
34 |

35 |
42 |
43 |
{game.name}
44 |
)
45 | }
46 |
47 |
48 | )
49 | }
50 |
51 | gameList.propTypes = {
52 | games: PropTypes.object.isRequired
53 | };
54 |
55 | export default gameList;
--------------------------------------------------------------------------------
/src/components/movieList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types';
3 | import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
4 | import 'react-circular-progressbar/dist/styles.css';
5 | import {Link} from 'react-router-dom';
6 | import ContentLoader from 'react-content-loader'
7 |
8 | const movieList = ({ movies }) => { //no props => ({movies})
9 | return (
10 |
11 |
12 |
FİLMLER
13 | Film Ekle
14 |
15 |
16 | {movies.movieList.length == 0 &&
17 |
18 | {[...Array(6)].map((x, i) =>
19 |
20 |
21 |
22 |
23 | )}
24 |
25 | }
26 |
27 |
28 | {
29 | movies.movieList.map(movie =>
30 |
31 |
32 |

33 |
40 |
41 |
{movie.name}
42 |
)
43 | }
44 |
45 |
46 | )
47 | }
48 |
49 | movieList.propTypes = {
50 | movies: PropTypes.object.isRequired
51 | };
52 |
53 | export default movieList;
--------------------------------------------------------------------------------
/src/components/search.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import {Link} from 'react-router-dom';
3 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
4 | import searchIcon from '../assets/images/search.svg';
5 | import { debounce } from 'lodash'
6 |
7 | //actions
8 | import {searchGames, searchMovies, searchActiveOverlay, searchDisableOverlay, searchListClear } from '../actions/search'
9 |
10 | class search extends PureComponent {
11 | state = {
12 | loading:false,
13 | noResult:false
14 | }
15 |
16 | searchText = debounce(async(search) => {
17 | if(search.target.value.length >= 3){
18 | this.setState({loading:true})
19 | await this.props.searchGames(search.target.value) // games action
20 | await this.props.searchMovies(search.target.value) // movies action
21 | this.setState({loading:false})
22 | this.props.searchActiveOverlay();
23 | }
24 | //lenght 0 ise search array temizle
25 | if(search.target.value.length < 3){
26 | this.setState({loading:false})
27 | this.props.searchDisableOverlay();
28 | setTimeout(() => {
29 | this.props.searchListClear();
30 | }, 200);
31 | }
32 | },600)
33 |
34 | closeSearch = () => { //setState görmesi için arrowFunction kullanılması gerekli.
35 | this.searchInput.value = ""; //ref={(el) => (this.searchInput = el)}
36 | }
37 |
38 | render() {
39 | return (
40 |
41 |
42 | {!this.state.loading &&
43 |

44 | }
45 | {this.state.loading &&
46 |
47 | }
48 |
(this.searchInput = el)}
49 | onChange={e => this.searchText(e)}/>
50 |
51 |
52 |
53 | {this.props.searchListGames.length == 0 && this.props.searchListMovie.length == 0 &&
54 |
Aradığınız kelimeye ait oyun yada film bulunamadı..
55 | }
56 |
57 | {/* Games search */}
58 | {this.props.searchListGames.map(searchItem =>
59 |
60 |
61 |

62 |
63 |
{searchItem.name} ({searchItem.year})
64 |
{searchItem.category}
65 |
66 |
67 |
70 |
71 | )}
72 |
73 | {/* Movie search */}
74 | {this.props.searchListMovie.map(searchItem =>
75 |
76 |
77 |

78 |
79 |
{searchItem.name} ({searchItem.year})
80 |
{searchItem.category}
81 |
82 |
83 |
86 |
87 | )}
88 |
89 |
90 |
91 | )
92 | }
93 | }
94 |
95 | const mapStateToProps = (state) => { //state => state.games --> games bana rootReducerdan geliyor..
96 | return {
97 | searchListGames: state.search.searchListGame,
98 | searchListMovie: state.search.searchListMovie,
99 | boolSearchOverlay: state.search.searchOverlay
100 | }
101 | }
102 |
103 | const mapDispatchToProps = {
104 | searchGames,
105 | searchMovies,
106 | searchActiveOverlay,
107 | searchDisableOverlay,
108 | searchListClear
109 | }
110 |
111 | export default connect(mapStateToProps,mapDispatchToProps) (search);
112 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import reportWebVitals from './reportWebVitals'
5 |
6 | import { createStore, applyMiddleware } from 'redux';
7 | import { createLogger } from 'redux-logger'
8 | import thunk from 'redux-thunk'
9 | import { createPromise } from 'redux-promise-middleware'
10 |
11 | import rootReducer from './reducers/rootReducer';
12 | import { Provider } from 'react-redux';
13 | import { BrowserRouter } from 'react-router-dom';
14 | import Header from './layout/header.jsx';
15 |
16 | const store = createStore(
17 | rootReducer,
18 | applyMiddleware(createPromise(), thunk, createLogger())
19 | );
20 |
21 | // + Provider:
22 | // - Tüm Redux ın projemizde dahil olabilmesi için oluşturmuş olduğumuz Provider özelliğini App Componenti dışına ekliyoruz.
23 | // - Ayrıca oluşturmuş olduğumuz store un her yere ulaşabilmesi için Provider yapısına store ekliyoruz.
24 |
25 | // + Router yapısı: Tüm projenin router'ı görebilmesi için bu kısımda provider üzerinde sarmaladık.
26 | ReactDOM.render(
27 |
28 |
29 |
30 |
31 |
32 | ,
33 | document.getElementById('root')
34 | );
35 |
36 | // If you want to start measuring performance in your app, pass a function
37 | // to log results (for example: reportWebVitals(console.log))
38 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
39 | reportWebVitals();
40 |
--------------------------------------------------------------------------------
/src/layout/header.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import '../assets/scss/layout/header.scss'
3 | import logo from '../assets/images/logo.png';
4 | //Components
5 | import Search from '../components/search.jsx'
6 |
7 | class header extends PureComponent {
8 | state = {
9 | isDark:false
10 | }
11 | toggleSwitch = () => {
12 | this.setState({isDark: !this.state.isDark})
13 | if(!this.state.isDark) document.body.classList.add("dark");
14 | else document.body.classList.remove("dark");
15 | }
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 |

24 |
25 |
26 |
27 |
28 |
36 |
37 |
38 |
39 | )
40 | }
41 | }
42 |
43 | export default header;
44 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/productCreate.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 |
3 | //Components
4 | import CreateGame from '../components/createGame'
5 | import CreateMovie from '../components/createMovie'
6 |
7 | //Class component
8 | class productCreate extends PureComponent {
9 | render() {
10 | return (
11 |
12 | {this.props.match.params.platform == "game" &&
13 |
14 | }
15 | {this.props.match.params.platform == "movie" &&
16 |
17 | }
18 |
19 | )
20 | }
21 | //header arka plan rengi kaldırmak için..
22 | componentWillMount(){ //component yükleniyorken
23 | document.body.classList.add("header-nobg");
24 |
25 | }
26 | componentWillUnmount(){ //component çıkılıyorken
27 | document.body.classList.remove("header-nobg");
28 | }
29 | }
30 |
31 | export default productCreate;
32 |
--------------------------------------------------------------------------------
/src/pages/productDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
3 |
4 | //Component
5 | import Detail from '../components/detail.jsx'
6 | //Actions
7 | import {detail} from '../actions/games';
8 |
9 | class productDetail extends PureComponent {
10 | render() {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | //header arka plan rengi kaldırmak için..
19 | componentWillMount(){ //component yükleniyorken
20 | document.body.classList.add("header-nobg");
21 |
22 | }
23 | componentWillUnmount(){ //component çıkılıyorken
24 | document.body.classList.remove("header-nobg");
25 | }
26 |
27 | componentDidMount(){ //Created
28 | const platform = this.props.match.params.platform //params platform
29 | const slug = this.props.match.params.slug //params id
30 | this.props.detail(platform,slug); //actiona gameId ve platform adı gönderdik.
31 | }
32 | }
33 |
34 | const mapStateToProps = (state) => { //state => state.games --> games bana rootReducerdan geliyor..
35 | return {
36 | detailInfo: state.games.detail
37 | }
38 | }
39 |
40 | const mapDispatchToProps = {
41 | detail
42 | }
43 |
44 | export default connect(mapStateToProps,mapDispatchToProps) (productDetail);
45 |
--------------------------------------------------------------------------------
/src/pages/productEdit.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 |
3 | //Components
4 | import {connect} from 'react-redux' //redux ile bağlantı kurmak için connect gerekli.
5 | import EditGame from '../components/editGame'
6 | import EditMovie from '../components/editMovie'
7 | import {detail} from '../actions/games'
8 |
9 | //Class component
10 | class productEdit extends PureComponent {
11 | render() {
12 | return (
13 |
14 | {this.props.detailInfo.platform == "games" &&
15 |
16 | }
17 |
18 | {this.props.detailInfo.platform == "movies" &&
19 |
20 | }
21 |
22 | )
23 | }
24 |
25 | componentDidMount(){ //Created
26 | document.body.classList.add("header-nobg");
27 | const platform = this.props.match.params.platform //params platform
28 | const slug = this.props.match.params.slug //params id
29 | this.props.detail(platform,slug); //actiona gameId ve platform adı gönderdik.
30 | }
31 | }
32 |
33 | const mapStateToProps = (state) => { //state => state.games --> games bana rootReducerdan geliyor..
34 | return {
35 | detailInfo: state.games.detail
36 | }
37 | }
38 |
39 | const mapDispatchToProps = {
40 | detail
41 | }
42 |
43 | export default connect(mapStateToProps,mapDispatchToProps) (productEdit);
44 |
45 |
--------------------------------------------------------------------------------
/src/pages/productPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import propTypes from "prop-types";
3 | import { connect } from "react-redux"; //redux ile bağlantı kurmak için connect gerekli.
4 |
5 | //Component
6 | import GameList from "../components/gameList.jsx";
7 | import MovieList from "../components/movieList.jsx";
8 |
9 | //Actions
10 | import { fetchMovies } from "../actions/movies";
11 | import { clearDetail, fetchGames } from "../actions/games";
12 | import { searchDisableOverlay } from "../actions/search";
13 |
14 | class moviesPage extends PureComponent {
15 | static propTypes = {
16 | movies: propTypes.object.isRequired, //hem obje olacak hem de gerekli olacak. //movies = { this.props.movies} propstype adı üstüne..
17 | games: propTypes.object.isRequired, //hem obje olacak hem de gerekli olacak. //games = {this.props.games}
18 | };
19 |
20 | render() {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | //Created
33 | componentDidMount() {
34 | this.props.fetchMovies(); //action
35 | this.props.fetchGames(); //action
36 | this.props.clearDetail(); //action detay state temizle
37 | }
38 |
39 | //Leave Page
40 | componentWillUnmount(){
41 | this.props.searchDisableOverlay();
42 | }
43 |
44 | closeOverlay = () => {
45 | this.props.searchDisableOverlay();
46 | }
47 | }
48 |
49 | const mapStateToProps = (state) => {
50 | return {
51 | movies: state.movies,
52 | games: state.games,
53 | boolSearchOverlay: state.search.searchOverlay
54 | };
55 | };
56 |
57 | const mapDispatchToProps = {
58 | fetchMovies,
59 | fetchGames,
60 | clearDetail,
61 | searchDisableOverlay
62 | };
63 |
64 | export default connect(mapStateToProps, mapDispatchToProps)(moviesPage);
65 |
--------------------------------------------------------------------------------
/src/reducers/games.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | gameList: [],
3 | detail:[],
4 | createGameResult:''
5 | }
6 |
7 | export default (state = initialState, action) => {
8 | switch (action.type) {
9 | case "FETCH_GAMES":
10 | return{
11 | ...state,
12 | gameList: action.payload
13 | }
14 | case "SET_DETAIL":
15 | return{
16 | ...state,
17 | detail: action.payload
18 | }
19 | case "EDIT_RESULT":
20 | return{
21 | ...state,
22 | createGameResult: action.payload.statusText,
23 | detail: action.payload.data
24 | }
25 | case "NEW_GAME_RESULT":
26 | return{
27 | ...state,
28 | createGameResult: action.payload
29 | }
30 | case "CLEAR_DETAIL":
31 | return{
32 | ...state,
33 | detail: ''
34 | }
35 | default:
36 | return state;
37 | }
38 | }
--------------------------------------------------------------------------------
/src/reducers/movies.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | movieList: [],
3 | createMovieResult:'',
4 | searchOverlay:''
5 | }
6 |
7 | export default (state = initialState, action) => {
8 | switch (action.type) {
9 | case "FETCH_MOVIES":
10 | return{
11 | ...state,
12 | movieList: action.payload
13 | }
14 | case "NEW_MOVIE_RESULT":
15 | return{
16 | ...state,
17 | createMovieResult: action.payload
18 | }
19 |
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux'
2 | import movies from './movies'
3 | import games from './games'
4 | import search from './search'
5 |
6 | export default combineReducers({
7 | movies,
8 | games,
9 | search
10 | })
--------------------------------------------------------------------------------
/src/reducers/search.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | searchOverlay:'',
3 | searchListGame:[],
4 | searchListMovie:[],
5 | }
6 |
7 | export default (state = initialState, action) => {
8 | switch (action.type) {
9 | case "SEARCH_GAME_RESULT":
10 | return{
11 | ...state,
12 | searchListGame: action.payload
13 | }
14 | case "SEARCH_MOVIE_RESULT":
15 | return{
16 | ...state,
17 | searchListMovie: action.payload
18 | }
19 | case "ACTIVE_SEARCH_OVERLAY":
20 | return{
21 | ...state,
22 | searchOverlay: action.payload
23 | }
24 | case "DISABLED_SEARCH_OVERLAY":
25 | return{
26 | ...state,
27 | searchOverlay: action.payload
28 | }
29 | case "SEARCH_LIST_CLEAR":
30 | return{
31 | ...state,
32 | searchListGame:[],
33 | searchListMovie :[]
34 | }
35 | default:
36 | return state;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------