Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Sanjay Namdeo #34

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"presets": ["env", "react"]
"presets": ["env", "react","es2015", "stage-0"],
"plugins": ["babel-plugin-transform-object-rest-spread"]
}
21 changes: 17 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,27 @@
"build": "webpack --mode production"
},
"dependencies": {
"react": "^16.3.1",
"react-dom": "^16.3.1"
"async": "^3.2.0",
"axios": "^0.19.2",
"babel": "^6.23.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"moment": "^2.26.0",
"react": "^16.13.1",
"react-dom": "^16.3.1",
"react-icons": "^3.10.0",
"react-redux": "6.0.0",
"react-router-dom": "^5.2.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"babel-core": "6.26.*",
"babel-loader": "7.1.*",
"babel-core": "^6.26.3",
"babel-loader": "7",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "1.7.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "6.24.*",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "2.1.*",
"html-loader": "0.5.*",
"html-webpack-plugin": "3.2.*",
Expand Down
34 changes: 27 additions & 7 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,36 @@ import React from 'react';
import ReactDOM from 'react-dom';

import Header from './Header.jsx';
import {applyMiddleware, createStore} from "redux";
import thunk from "redux-thunk";
import {Provider} from "react-redux";
import {Route, Router} from "react-router-dom";
import reducers from "./reducers";
import ActivityFeed from "./components/ActivityFeed";
import ActivityDetail from "./components/ActivityDetail";
import history from "./history";

const App = () => {
return (
<div className='container'>
<Header/>
<div className="container-view">Some activities should be here</div>
</div>
);
return (
<div className='container'>
<Router history={history}>
<Header/>
<div className="container-view">
<Route path='/' exact component={ActivityFeed}/>
<Route path='/activity/:id' component={ActivityDetail}/>
</div>
</Router>
</div>
);
};

ReactDOM.render(<App/>, document.getElementById('app'));
const store = createStore(reducers, applyMiddleware(thunk));

ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('app')
);

export default App;
39 changes: 39 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import aircall from "../apis/aircall";
import {ARCHIVE_CALL, FETCH_ACTIVITY_DETAIL, FETCH_ACTIVITIES} from "./types";

const fetchActivities = () => async (dispatch) => {
// Fetch all activities
const response = await aircall.get('/activities');

// Dispatch FETCH_ACTIVITIES to invoke reducer to update activities state
dispatch({
type: FETCH_ACTIVITIES,
payload: response.data
});
}

const fetchActivityDetail = (activityId) => async (dispatch) => {
// Fetch activity details by activity id
const response = await aircall.get(`/activities/${activityId}`);

// Dispatch FETCH_ACTIVITY_DETAIL to update activity state
dispatch({
type: FETCH_ACTIVITY_DETAIL,
payload: response.data
});
}

const archiveCall = (id) => async (dispatch) => {
const archiveStatus = {is_archived: true}

// Post call to update archive status to true for the selected call by id
await aircall.post(`/activities/${id}`, archiveStatus);

// Dispatch ARCHIVE_CALL to update calls state and remove archived call
dispatch({
type: ARCHIVE_CALL,
payload: id
});
}

export {fetchActivities, fetchActivityDetail, archiveCall};
3 changes: 3 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const FETCH_ACTIVITIES = 'FETCH_CALLS';
export const FETCH_ACTIVITY_DETAIL = 'FETCH_CALL_DETAILS';
export const ARCHIVE_CALL = 'ARCHIVE_CALL';
5 changes: 5 additions & 0 deletions src/apis/aircall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import axios from 'axios';

export default axios.create({
baseURL: 'https://aircall-job.herokuapp.com'
});
54 changes: 54 additions & 0 deletions src/components/ActivityDetail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {archiveCall, fetchActivityDetail} from '../actions';
import moment from "moment";
import history from "../history";
import ActivityCard from "./activities/activity-card/ActivityCard";

class ActivityDetail extends Component {
componentDidMount() {
// Get call id from props parameter match
const id = this.props.match.params.id;
// invoke redux action to fetch details of the selected call by id
this.props.fetchActivityDetailAction(id);
}

// Convert seconds to minutes and seconds
secondsToMinutes = (seconds) => (seconds - (seconds %= 60)) / 60 + (9 < seconds ? ':' : ':0') + seconds;

// Archive call by call id
onArchiveClick = (id) => {
this.props.archiveCallAction(id);
history.push('/');
}

renderActivityDetail = () => {
const {direction, call_type, duration, via, to, from, created_at, id, is_archived} = this.props.activity;

return (
<ActivityCard
number={direction === 'outbound' ? to : from}
recipient={direction === 'outbound' ? from : to}
call_type={call_type === 'missed' ? 'Missed call' : call_type === 'voicemail' ? 'VoiceMail' : 'Received'}
duration={this.secondsToMinutes(duration)}
via={via}
date={moment(created_at).format('LL')}
onClick={() => this.onArchiveClick(id)}
is_archived={is_archived}
/>
);
};

render() {
return this.renderActivityDetail();
}
}

const mapStateToProps = (state) => {
return {activity: state.activity};
}

export default connect(mapStateToProps, {
fetchActivityDetailAction: fetchActivityDetail,
archiveCallAction: archiveCall
})(ActivityDetail);
52 changes: 52 additions & 0 deletions src/components/ActivityFeed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {fetchActivities} from '../actions';
import moment from "moment";
import ActivityList from "./activities/activity-list/ActivityList";

class ActivityFeed extends Component {
componentDidMount() {
// Retrieve all activities
this.props.fetchActivitiesAction();
}

// Function to prepare an object with mappings of activities group by date
groupActivitiessByDate = () => {
const activitiesDateMap = {};
const activeCalls = this.props.activities.filter(activity => activity.is_archived !== true);
for (let call of activeCalls) {
const date = moment(call.created_at).format('LL');
const callList = activitiesDateMap[date] || [];
callList.push(call);
activitiesDateMap[date] = callList;
}
return activitiesDateMap;
}

renderActivitiesList = () => {
const activitiesByDate = this.groupActivitiessByDate();
// If no activities registered(OR all archived)
if(Object.keys(activitiesByDate).length === 0) {
return <h1>No activities registered</h1>;
}

// Show an activity card for each activity
return Object.keys(activitiesByDate).map((activityDate, index) => {
return (
<ActivityList
date={activityDate}
calls={activitiesByDate[activityDate]}
key={index}
/>
);
});
}

render = () => this.renderActivitiesList();
}

const mapStateToProps = (state) => {
return {activities: state.activities};
}

export default connect(mapStateToProps, {fetchActivitiesAction: fetchActivities})(ActivityFeed);
59 changes: 59 additions & 0 deletions src/components/activities/activity-card/ActivityCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.activity-card {
margin: 10px;
padding: 10px;
background-color: #FFFFFF;
border: solid 1px #E8E8E7;
border-radius: 8px;
}

.activity-card .title {
text-align: center;
font-size: 1.5em;
font-weight: 500;
margin: 32px 0;
}

.activity-card .data-line {
margin: 20px 0;
}

.activity-card .data-line .label {
text-transform: uppercase;
font-weight: bold;
text-align: center;
font-size: small;
padding-bottom: 8px;
}

.data-line .label {
text-transform: uppercase;
font-weight: bold;
text-align: center;
}

.activity-card .data-line .value {
font-size: 1.1em;
text-align: center;
}

.activity-card .archived {
color: #FC5624;
margin-top: 50px;
font-weight: 500;
text-align: center;
font-size: 1.1em;
}

.activity-card .button {
font-size: 1.1em;
font-weight: 700;
width: 100%;
height: 40px;
border-radius: 8px;
cursor: pointer;
margin-top: 10px;
}

.activity-card .button:hover {
background-color: #bcbcbc;
}
39 changes: 39 additions & 0 deletions src/components/activities/activity-card/ActivityCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import '../../../css/body.css';
import history from "../../../history";
import './ActivityCard.css';

const ActivityCard = (props) => {
const {call_type, duration, via, recipient, number, date, is_archived} = props;

return (
<div className="activity-card">
<h2 className="title">{recipient ? recipient : 'Unknown'}</h2>
<div className='data-line'>
<div className="label">Date</div>
<div className="value">{date}</div>
</div>
<div className='data-line'>
<div className="label">Duration</div>
<div className="value">{duration}</div>
</div>
<div className='data-line'>
<div className="label">Type</div>
<div className="value">{call_type}</div>
</div>
<div className='data-line'>
<div className="label">Number</div>
<div className="value">{number}</div>
</div>
<div className='data-line'>
<div className="label">Via</div>
<div className="value">{via}</div>
</div>
<div className="archived">{is_archived}</div>
<button className='button' onClick={props.onClick}>Archive</button>
<button className='button' onClick={() => history.push('/')}>Back</button>
</div>
);
};

export default ActivityCard;
7 changes: 7 additions & 0 deletions src/components/activities/activity-list/ActivityList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.activity-date {
color: #8f8f8f;
text-align: center;
text-transform: uppercase;
font-weight: 500;
margin-top: 8px;
}
18 changes: 18 additions & 0 deletions src/components/activities/activity-list/ActivityList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import './ActivityList.css';
import ActivityItem from "./activity-item/ActivityItem";

const ActivityList = ({date, calls}) => {
const callDetails = calls.map((call) => {
return <ActivityItem call={call} key={call.id}/>
});

return (
<div>
<div className='activity-date'>{date}</div>
<div>{callDetails}</div>
</div>
);
};

export default ActivityList;
Loading