Skip to content

Commit bfee054

Browse files
authored
Merge pull request #115 from ClickPop/release/1.0.0-alpha.8
Version 1.0.0-alpha.8
2 parents 80a6f45 + eeaa980 commit bfee054

File tree

9 files changed

+386
-3
lines changed

9 files changed

+386
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ yarn-debug.log*
2424
yarn-error.log*
2525

2626
.eslintcache
27+
.env

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "backtalk-ui",
3-
"version": "1.0.0-alpha.7",
3+
"version": "1.0.0-alpha.8",
44
"private": true,
55
"dependencies": {
66
"axios": "^0.20.0",
@@ -16,6 +16,7 @@
1616
"react-dom": "^16.13.1",
1717
"react-feather": "^2.0.9",
1818
"react-gtm-module": "^2.0.11",
19+
"react-map-gl": "^5.2.10",
1920
"react-moment": "^1.0.0",
2021
"react-router-dom": "^5.2.0",
2122
"react-scripts": "3.4.3",

src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import packageJson from '../package.json';
1818
import TagManager from 'react-gtm-module';
1919
import { PasswordResetStart } from './views/PasswordResetStart';
2020
import { PasswordReset } from './views/PasswordReset';
21+
import { Admin } from './views/Admin';
2122
import { ShareResponses } from './views/ShareResponses';
2223

2324
if (process.env.NODE_ENV === 'production') {
@@ -93,6 +94,7 @@ const App = () => {
9394
component={FirstSurvey}
9495
/>
9596
<ProtectedRoute exact path="/dashboard" component={Dashboard} />
97+
<ProtectedRoute exact path="/admin" component={Admin} />
9698
<ProtectedRoute
9799
exact
98100
path="/responses/:hash"

src/components/ResponseMap.jsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { useState } from 'react';
2+
import ReactMapGL, { Marker } from 'react-map-gl';
3+
const apiKey = process.env.REACT_APP_MAPBOX_KEY;
4+
5+
export const ResponseMap = ({ responses }) => {
6+
const [latitude, longitude] = responses.filter((r) => r.geo)[0]?.geo?.ll;
7+
const [viewport, setViewport] = useState({
8+
width: '100%',
9+
height: 200,
10+
latitude,
11+
longitude,
12+
zoom: 1,
13+
});
14+
15+
return (
16+
<div className="mb-4 rounded-3 overflow-hidden">
17+
<ReactMapGL
18+
mapboxApiAccessToken={apiKey}
19+
{...viewport}
20+
mapStyle="mapbox://styles/mapbox/streets-v8"
21+
onViewportChange={(nextViewport) => setViewport(nextViewport)}
22+
>
23+
{responses
24+
.filter((r) => r.geo && r.geo.ll)
25+
.map((response) => (
26+
<Marker
27+
key={response.id}
28+
latitude={response.geo.ll[0]}
29+
longitude={response.geo.ll[1]}
30+
offsetLeft={viewport.zoom - 10}
31+
offsetTop={viewport.zoom - 10}
32+
>
33+
<span role="img" aria-label="reponse map pin">
34+
📍
35+
</span>
36+
</Marker>
37+
))}
38+
</ReactMapGL>
39+
</div>
40+
);
41+
};

src/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import './scss/index.scss';
4+
import 'mapbox-gl/dist/mapbox-gl.css';
45
import App from './App';
56
import 'bootstrap';
67
import 'popper.js';

src/views/Admin.jsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as axios from 'axios';
2+
import React, { useContext, useEffect, useState } from 'react';
3+
import Moment from 'react-moment';
4+
import { Card } from '../components/Card';
5+
import { context } from '../context/Context';
6+
7+
export const Admin = () => {
8+
const [view, setView] = useState(null);
9+
const [users, setUsers] = useState([]);
10+
const [surveys, setSurveys] = useState([]);
11+
const [userCount, setUserCount] = useState(0);
12+
const [surveyCount, setSurveyCount] = useState(0);
13+
const [responseCount, setResponseCount] = useState(0);
14+
const { state } = useContext(context);
15+
useEffect(() => {
16+
const fetchData = async () => {
17+
const totalUsers = await axios.get('/api/v1/admin/users', {
18+
headers: { Authorization: `Bearer ${state.token}` },
19+
});
20+
21+
const totalSurveys = await axios.get('/api/v1/admin/surveys', {
22+
headers: { Authorization: `Bearer ${state.token}` },
23+
});
24+
25+
setUserCount(totalUsers.data.results.length);
26+
setSurveyCount(totalSurveys.data.results.length);
27+
setResponseCount(
28+
totalSurveys.data.results.reduce(
29+
(sum, survey) => survey.Responses.length + sum,
30+
0,
31+
),
32+
);
33+
setUsers(totalUsers.data.results);
34+
setSurveys(totalSurveys.data.results);
35+
};
36+
37+
fetchData();
38+
}, [state.token]);
39+
40+
return (
41+
<div className="container">
42+
<div className="row">
43+
<div className="col-12 order-sm-2 col-sm-6 col-lg-4">
44+
<h2 className="h3 mb-3">Show me</h2>
45+
{users.length > 0 && (
46+
<div>
47+
<p
48+
className="card-text"
49+
onClick={() => setView('users')}
50+
style={{ cursor: 'pointer' }}
51+
>
52+
Users: {userCount}
53+
</p>
54+
<p
55+
className="card-text"
56+
onClick={() => setView('surveys')}
57+
style={{ cursor: 'pointer' }}
58+
>
59+
Surveys: {surveyCount}
60+
</p>
61+
<p className="card-text">Responses: {responseCount}</p>
62+
</div>
63+
)}
64+
</div>
65+
<div className="col-12 order-sm-1 col-sm-6 col-lg-8 pr-sm-4">
66+
<h1 className="mb-4">Results</h1>
67+
{view === 'users' &&
68+
users &&
69+
users.map((user) => (
70+
<Card key={user.id} title={user.name} className="mb-2">
71+
<p className="card-text">Email: {user.email}</p>
72+
<p className="text-muted">
73+
<strong>
74+
Sign-up Date:{' '}
75+
<Moment format="MMM D, YYYY">{user.createdAt}</Moment>{' '}
76+
<Moment format="h:mm a">{user.createdAt}</Moment>
77+
</strong>
78+
</p>
79+
</Card>
80+
))}
81+
{view === 'surveys' &&
82+
surveys &&
83+
surveys.map((survey) => (
84+
<Card key={survey.id} title={survey.title} className="mb-2">
85+
<p className="card-text">
86+
Questions: {survey.Questions.length}
87+
</p>
88+
<p className="card-text">
89+
Responses: {survey.Responses.length}
90+
</p>
91+
<p className="text-muted">
92+
<strong>
93+
Created At:{' '}
94+
<Moment format="MMM D, YYYY">{survey.createdAt}</Moment>{' '}
95+
<Moment format="h:mm a">{survey.createdAt}</Moment>
96+
</strong>
97+
</p>
98+
</Card>
99+
))}
100+
</div>
101+
</div>
102+
</div>
103+
);
104+
};

src/views/Changelog.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ const Changelog = () => {
2020
A list of all the ways Backtalk is making it easier to get the
2121
answers you need, quickly.
2222
</p>
23+
<ChangelogEntry
24+
title="Response Map"
25+
date="December 9, 2020"
26+
description="We added a map so you can get a better idea of where your responses are coming from."
27+
/>
28+
2329
<ChangelogEntry
2430
title="Click to Copy"
2531
date="December 6, 2020"

src/views/Responses.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import anonymousNickname from '../helpers/anonymousNickname';
99
import { EditInPlaceInput } from '../components/EditInPlaceInput';
1010
import { ResponseCard } from '../components/ResponseCard';
1111
import { CopyLink } from '../components/CopyLink';
12+
import { ResponseMap } from '../components/ResponseMap';
1213

1314
export const Responses = () => {
1415
const params = useParams();
@@ -291,6 +292,7 @@ export const Responses = () => {
291292
<div className="row">
292293
<div className="col-12 order-sm-2 col-sm-6 col-lg-4">
293294
<div className="mb-4">
295+
{responses?.length > 0 && <ResponseMap responses={responses} />}
294296
<h3 className="h5">Share Survey</h3>
295297
<CopyLink
296298
to={`/survey/${params.hash}`}

0 commit comments

Comments
 (0)