Skip to content
This repository was archived by the owner on Sep 1, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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