diff --git a/reactjs/components/Error.js b/reactjs/components/Error.js
new file mode 100644
index 0000000..61d373c
--- /dev/null
+++ b/reactjs/components/Error.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const Error = () => {
+ return (
+
+
There was an error loading your gifs, please try again later...
+
+ )
+}
+export default Error;
\ No newline at end of file
diff --git a/reactjs/components/GifPages/index.js b/reactjs/components/GifPages/index.js
new file mode 100644
index 0000000..aef0c97
--- /dev/null
+++ b/reactjs/components/GifPages/index.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import Gifs from '../../components/Gifs';
+
+const GifPages = () => (
+
+
+
+
+
+
+);
+
+export default GifPages;
diff --git a/reactjs/components/HelloFriend/style.scss b/reactjs/components/GifPages/style.scss
similarity index 100%
rename from reactjs/components/HelloFriend/style.scss
rename to reactjs/components/GifPages/style.scss
diff --git a/reactjs/components/Gifs.js b/reactjs/components/Gifs.js
new file mode 100644
index 0000000..8a078ff
--- /dev/null
+++ b/reactjs/components/Gifs.js
@@ -0,0 +1,94 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { gifsRequest, gotoNextPage } from '../store/actions/Gifs';
+import Loading from './Loading';
+import Error from './Error';
+import GifData from './gifdata/GifData';
+
+class Gifs extends Component {
+
+ state = {
+ currentPage: 0,
+ numItemsToDisplay: 12,
+ response: null,
+ slicedItems: null,
+ pageNum: null,
+ error: null,
+ totalItems: 0
+ }
+
+
+ componentDidMount() {
+ this.props.gifsRequest();
+ }
+
+ getSlicedItems = (page, displayItems, response) => {
+ let start = page * displayItems;
+ let end = start + displayItems;
+ let slicedItems = response.slice(start, end);
+ return slicedItems;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { currentPage, numItemsToDisplay, response } = this.state;
+ if (nextProps.response != null) {
+ let slicedItems = this.getSlicedItems(currentPage, numItemsToDisplay, nextProps.response);
+ this.setState({
+ response: nextProps.response,
+ slicedItems: slicedItems,
+ totalItems: nextProps.response.length
+ })
+ }
+ if (nextProps.pageNum != null) {
+ let slicedItems = this.getSlicedItems(nextProps.pageNum, numItemsToDisplay, response);
+ this.setState({
+ currentPage: nextProps.pageNum,
+ slicedItems: slicedItems,
+ pageNum: nextProps.pageNum
+ })
+ }
+ }
+
+ render() {
+ const { response, gotoNextPage, pageNum, error } = this.props;
+ const { slicedItems, numItemsToDisplay, totalItems, currentPage } = this.state;
+
+ if (response == null && pageNum == null && error == null) {
+ return (
+
+
+
+ )
+ } else if ((response !== null || pageNum != null) && error == null) {
+ return (
+
+
+
+ )
+ }
+ else if (response == null && pageNum == null && error != null) {
+ return (
+
+
+
+ )
+ } else {
+ return null;
+ }
+ }
+}
+const mapStateToProps = (state) => ({
+ response: state.gifs.response,
+ error: state.gifs.error,
+ pageNum: state.gifs.pageNum
+})
+
+const mapDispatchToProps = {
+ gifsRequest, gotoNextPage
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Gifs);
\ No newline at end of file
diff --git a/reactjs/components/HelloFriend/index.js b/reactjs/components/HelloFriend/index.js
deleted file mode 100644
index e8f3965..0000000
--- a/reactjs/components/HelloFriend/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-
-const HelloFriend = () => (
-
- Hello, friend.
-
- Have a great day :)
-
-
-);
-
-export default HelloFriend;
diff --git a/reactjs/components/HtmlHead/index.js b/reactjs/components/HtmlHead/index.js
index 3e9d76b..37b4d93 100644
--- a/reactjs/components/HtmlHead/index.js
+++ b/reactjs/components/HtmlHead/index.js
@@ -22,6 +22,7 @@ const HtmlHead = () => {
SystemSeed Technical Assessment
+
diff --git a/reactjs/components/Loading.js b/reactjs/components/Loading.js
new file mode 100644
index 0000000..1aeb379
--- /dev/null
+++ b/reactjs/components/Loading.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const Loading = () => {
+ return (
+
+
Loading your gifs...
+
+ )
+}
+export default Loading;
\ No newline at end of file
diff --git a/reactjs/components/gifdata/GifData.js b/reactjs/components/gifdata/GifData.js
new file mode 100644
index 0000000..967d8be
--- /dev/null
+++ b/reactjs/components/gifdata/GifData.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import GifGrid from './GifGrid';
+import Pagination from '../pagination/Pagination';
+
+const GifData = ({ slicedItems, totalItems, numItemsToDisplay, gotoNextPage }) => {
+ return (
+
+
+ {
+ numItemsToDisplay < totalItems &&
+
+ }
+
+ )
+}
+export default GifData;
\ No newline at end of file
diff --git a/reactjs/components/gifdata/GifGrid.js b/reactjs/components/gifdata/GifGrid.js
new file mode 100644
index 0000000..415f4a4
--- /dev/null
+++ b/reactjs/components/gifdata/GifGrid.js
@@ -0,0 +1,21 @@
+import React from 'react'
+
+const GifGrid = ({ slicedItems }) => {
+ return (
+
+ {
+ slicedItems.map(item => (
+
+
+

+
+
{item.title}
+
+
+
+ ))
+ }
+
+ )
+}
+export default GifGrid;
\ No newline at end of file
diff --git a/reactjs/components/pagination/Pagination.js b/reactjs/components/pagination/Pagination.js
new file mode 100644
index 0000000..0d38298
--- /dev/null
+++ b/reactjs/components/pagination/Pagination.js
@@ -0,0 +1,43 @@
+import React, { Component } from 'react';
+import PaginationItem from './PaginationItem';
+
+class Pagination extends Component {
+
+ constructor(props) {
+ super(props);
+ const { totalItems, numItemsToDisplay } = this.props;
+ this.totalItems = typeof totalItems === "number" ? totalItems : 0;
+ this.numItemsToDisplay = typeof numItemsToDisplay === "number" ? numItemsToDisplay : 0;
+ }
+
+ getPages(totalPage) {
+ let pages = [];
+ let i = 0;
+
+ while (i < totalPage) {
+ pages.push(i);
+ i++;
+ }
+
+ return pages;
+ }
+
+ handleClick = (page, e) => {
+ e.preventDefault();
+ this.props.gotoNextPage(page);
+ }
+
+ render() {
+ let pages = this.getPages(Math.ceil(this.totalItems / this.numItemsToDisplay));
+ return (
+
+ {
+ pages.map((page, index) =>
+
this.handleClick(page, e)} />
+ )
+ }
+
+ )
+ }
+}
+export default Pagination;
\ No newline at end of file
diff --git a/reactjs/components/pagination/PaginationItem.js b/reactjs/components/pagination/PaginationItem.js
new file mode 100644
index 0000000..54f2b0f
--- /dev/null
+++ b/reactjs/components/pagination/PaginationItem.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const PaginationItem = ({ page, onClick }) => {
+ return (
+
+ {page + 1}
+
+ )
+}
+export default PaginationItem;
\ No newline at end of file
diff --git a/reactjs/package.json b/reactjs/package.json
index fff583c..81f7eb6 100644
--- a/reactjs/package.json
+++ b/reactjs/package.json
@@ -3,6 +3,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
+ "axios": "^0.18.0",
"compression": "^1.7.3",
"express": "^4.16.3",
"next": "^6.1.1",
diff --git a/reactjs/pages/index.js b/reactjs/pages/index.js
index c6b3226..1184602 100644
--- a/reactjs/pages/index.js
+++ b/reactjs/pages/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import HelloFriend from '../components/HelloFriend';
+import GifPages from '../components/GifPages';
class Page extends React.Component {
static async getInitialProps() {
@@ -9,7 +9,7 @@ class Page extends React.Component {
}
render() {
- return ;
+ return ;
}
}
diff --git a/reactjs/store/actions/Gifs.js b/reactjs/store/actions/Gifs.js
new file mode 100644
index 0000000..4efee9b
--- /dev/null
+++ b/reactjs/store/actions/Gifs.js
@@ -0,0 +1,29 @@
+import * as types from './Types';
+
+export const gifsRequest = () => {
+ return {
+ type: types.GIFS_REQ,
+ payload: ''
+ }
+}
+
+export const gifsResponse = (data) => {
+ return {
+ type: types.GIFS_RES,
+ payload: data
+ }
+}
+
+export const gifsError = (data) => {
+ return {
+ type: types.GIFS_ERROR,
+ payload: data
+ }
+}
+
+export const gotoNextPage = (pageNum) => {
+ return {
+ type: types.GO_NEXT_PAGE,
+ payload: pageNum
+ }
+}
\ No newline at end of file
diff --git a/reactjs/store/actions/Types.js b/reactjs/store/actions/Types.js
new file mode 100644
index 0000000..b31b101
--- /dev/null
+++ b/reactjs/store/actions/Types.js
@@ -0,0 +1,5 @@
+export const GIFS_REQ = "GIFS_REQ";
+export const GIFS_RES = "GIFS_RES";
+export const GIFS_ERROR = "GIFS_ERROR";
+
+export const GO_NEXT_PAGE = "GO_NEXT_PAGE";
\ No newline at end of file
diff --git a/reactjs/store/actions/example.js b/reactjs/store/actions/example.js
deleted file mode 100644
index 761019e..0000000
--- a/reactjs/store/actions/example.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Increases custom value in store.
- */
-export const increaseValue = () => ({
- type: 'EXAMPLE_ACTION',
-});
diff --git a/reactjs/store/api/Api.js b/reactjs/store/api/Api.js
new file mode 100644
index 0000000..45b3089
--- /dev/null
+++ b/reactjs/store/api/Api.js
@@ -0,0 +1,7 @@
+import axios from 'axios';
+
+export default {
+ gif: {
+ gifs:() => axios.get('http://api.giphy.com/v1/gifs/trending?api_key=ovU9KlXS8RtZr0VgRT6X02ZaH02P30qw&limit=120')
+ }
+}
\ No newline at end of file
diff --git a/reactjs/store/reducers/GifReducer.js b/reactjs/store/reducers/GifReducer.js
new file mode 100644
index 0000000..326d3ee
--- /dev/null
+++ b/reactjs/store/reducers/GifReducer.js
@@ -0,0 +1,29 @@
+import * as types from '../actions/Types';
+
+const init = {
+ response: null,
+ error: null,
+ pageNum: null
+}
+
+const gifReducer = (state = init, action) => {
+ switch (action.type) {
+ case types.GIFS_REQ:
+ state = { ...state, response: null, error: null, pageNum: null }
+ break;
+ case types.GIFS_RES:
+ state = { ...state, response: action.payload, error: null, pageNum: null }
+ break;
+ case types.GIFS_ERROR:
+ state = { ...state, response: null, error: action.payload, pageNum: null }
+ break;
+ case types.GO_NEXT_PAGE:
+ state = { ...state, response: null, error: null, pageNum: action.payload }
+ break;
+ default:
+ break;
+ }
+ return state;
+}
+
+export default gifReducer;
\ No newline at end of file
diff --git a/reactjs/store/reducers/example.js b/reactjs/store/reducers/example.js
deleted file mode 100644
index de45043..0000000
--- a/reactjs/store/reducers/example.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export default (state = { count: 0 }, action) => {
- switch (action.type) {
- case 'EXAMPLE_ACTION':
- return {
- ...state,
- count: state.count + 1,
- };
-
- default:
- return state;
- }
-};
diff --git a/reactjs/store/reducers/index.js b/reactjs/store/reducers/index.js
index 72a8dcf..238d417 100644
--- a/reactjs/store/reducers/index.js
+++ b/reactjs/store/reducers/index.js
@@ -1,7 +1,7 @@
import { combineReducers } from 'redux';
// Put all your reducers here.
-import example from './example';
+import gifReducer from './GifReducer';
export default combineReducers({
- example,
-});
+ gifs: gifReducer
+});
\ No newline at end of file
diff --git a/reactjs/store/sagas/GifSaga.js b/reactjs/store/sagas/GifSaga.js
new file mode 100644
index 0000000..c9443a7
--- /dev/null
+++ b/reactjs/store/sagas/GifSaga.js
@@ -0,0 +1,17 @@
+import { call, takeLatest, put } from 'redux-saga/effects';
+import * as types from '../actions/Types';
+import api from '../api/Api';
+import { gifsResponse, gifsError } from '../actions/Gifs';
+
+export function* gifSagaWatcher() {
+ yield takeLatest(types.GIFS_REQ, goReqGif);
+}
+
+function* goReqGif() {
+ try {
+ let res = yield call(api.gif.gifs);
+ yield put(gifsResponse(res.data.data));
+ } catch (e) {
+ yield put(gifsError(e.response));
+ }
+}
diff --git a/reactjs/store/sagas/example.js b/reactjs/store/sagas/example.js
deleted file mode 100644
index 2f91211..0000000
--- a/reactjs/store/sagas/example.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { all, select, takeEvery } from 'redux-saga/effects';
-
-/**
- * React on example action being triggered.
- */
-function* exampleSaga() {
- const counter = yield select(reduxStore => reduxStore.example.count);
- yield console.log(`Saga triggered. New counter is ${counter}`);
-}
-
-/**
- * Main entry point for all example sagas.
- */
-export default function* exampleSagas() {
- yield all([
- yield takeEvery('EXAMPLE_ACTION', exampleSaga),
- ]);
-}
diff --git a/reactjs/store/sagas/index.js b/reactjs/store/sagas/index.js
index 6ff084d..de38dd8 100644
--- a/reactjs/store/sagas/index.js
+++ b/reactjs/store/sagas/index.js
@@ -1,9 +1,9 @@
-import { all } from 'redux-saga/effects';
+import { all, fork } from 'redux-saga/effects';
// Put all your sagas here.
-import exampleSagas from './example';
+import { gifSagaWatcher } from './GifSaga';
export default function* rootSaga() {
yield all([
- ...exampleSagas(),
- ]);
-}
+ fork(gifSagaWatcher)
+ ])
+}
\ No newline at end of file
diff --git a/reactjs/styles/base.scss b/reactjs/styles/base.scss
index 04a626a..357094b 100644
--- a/reactjs/styles/base.scss
+++ b/reactjs/styles/base.scss
@@ -1,3 +1,7 @@
+$layout_margin: 1%;
+$black_clr: #000000;
+$pagination_hover: #f4f4f4;
+
body {
font-family: sans-serif;
font-size: 16px;
@@ -6,4 +10,31 @@ body {
margin: 0;
padding: 0;
min-width: 250px;
+}
+
+.Pagination {
+ margin: $layout_margin;
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+ width: 100%;
+ &_Item {
+ padding: .5%;
+ position: relative;
+ cursor: pointer;
+ min-width: 3rem;
+ text-align: center;
+ border:1px solid #ced4da;
+ color: $black_clr;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
+ font-size: 1.2rem;
+ &:hover {
+ background-color: $pagination_hover;
+ }
+ &:active{
+ color: #445565 !important;
+ background-color: #e3e7eb !important;
+ border-color: #ced4da !important;
+ }
+ }
}
\ No newline at end of file