438 lines
14 KiB
JavaScript
438 lines
14 KiB
JavaScript
import React, { Component } from 'react';
|
|
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
|
|
import Modal from 'react-modal';
|
|
import { Link } from 'react-router-dom';
|
|
import Button from '@material-ui/core/Button';
|
|
import sortBy from 'sort-by';
|
|
|
|
// Custom components
|
|
import AppHeader from '../AppHeader';
|
|
import Footer from '../Footer';
|
|
import EnhancedTable from '../EnhancedTable';
|
|
import LayerMap from '../LayerMap';
|
|
import PreviewMap from '../PreviewMap';
|
|
|
|
// Helpers
|
|
import Client from '../../utils/feathers';
|
|
import { stripProtocol } from '../../utils/url';
|
|
import Countries from 'country-list';
|
|
|
|
// Images
|
|
import AmbassadorPin from '../../assets/img/map/ambassador_pin.png';
|
|
import LoadingGif from '../../assets/img/loading_icon.gif';
|
|
|
|
// List of countries
|
|
const countries = Countries();
|
|
|
|
const centerStyle = {
|
|
marginTop: 20,
|
|
marginBottom: 20
|
|
};
|
|
|
|
const loadingStyle = {
|
|
textAlign: 'center',
|
|
marginTop: 20,
|
|
marginBottom: 20,
|
|
display: 'block',
|
|
marginLeft: 'auto',
|
|
marginRight: 'auto'
|
|
};
|
|
|
|
const mapsStyles = {
|
|
content : {
|
|
top : '50%',
|
|
left : '50%',
|
|
right : 'auto',
|
|
bottom : 'auto',
|
|
marginRight : '-50%',
|
|
transform : 'translate(-50%, -50%)',
|
|
minWidth : '300px'
|
|
}
|
|
};
|
|
|
|
const columnData = [
|
|
{ id: 'nickname', numeric: false, disablePadding: true, label: 'Nickname' },
|
|
{ id: 'telegram', numeric: false, disablePadding: false, label: 'Telegram Account' },
|
|
{ id: 'keybase', numeric: false, disablePadding: false, label: 'Keybase' },
|
|
{ id: 'email', numeric: false, disablePadding: false, label: 'Email' },
|
|
{ id: 'phone', numeric: false, disablePadding: false, label: 'Phone' },
|
|
{ id: 'link', numeric: false, disablePadding: false, label: 'URL' },
|
|
{ id: 'location', numeric: false, disablePadding: true, label: 'Location' },
|
|
{ id: 'map', numeric: false, disablePadding: false, label: 'Maps', disableSearch: true}
|
|
];
|
|
|
|
/**
|
|
* Ambassador page component.
|
|
*/
|
|
class AmbassadorsPage extends Component {
|
|
constructor(props, context) {
|
|
super(props, context);
|
|
|
|
/** @type {ComponentState} */
|
|
this.state = {
|
|
ambassadors: {
|
|
total: 0,
|
|
limit: 0,
|
|
skip: 0,
|
|
data: []
|
|
},
|
|
ambassadorsSearch: [],
|
|
merchantMarkers: [],
|
|
loading: true,
|
|
rowsPerPage: [100,200,300],
|
|
numberOfRows: 100,
|
|
page: 1,
|
|
total: undefined,
|
|
mapsModalIsOpen: false,
|
|
mapsTitle: '',
|
|
mapsDescription: '',
|
|
mapsLat: 0,
|
|
mapsLon: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @description Lifecycle event handler called just after the App loads into the DOM.
|
|
*/
|
|
UNSAFE_componentWillMount() {
|
|
// Get the ambassadors list
|
|
this.getAmbassadors();
|
|
this.getMerchants();
|
|
}
|
|
|
|
fillResults(result) {
|
|
const data = result;
|
|
return (item) => data.data.push(item);
|
|
}
|
|
|
|
/**
|
|
* @description Get ambassadors from the web service
|
|
* @param {number} [limit=10] - Max items to be returned.
|
|
* @param {number} [skip=0] - Start index search
|
|
*/
|
|
getAmbassadors = async (limit = 50, skip = 0) => {
|
|
const app = this;
|
|
// Initially we don't know how much the total value is, so to make sure we enter the loop
|
|
// at least once we're just setting it to be 1
|
|
let total = 1;
|
|
|
|
const ambassadors = Client.service('api/v2/ambassadors');
|
|
this.setState({loading: true});
|
|
let result;
|
|
while(skip < total){
|
|
let partialResponse = await ambassadors.find({
|
|
query: {
|
|
$sort: { account: 1 },
|
|
$limit: limit,
|
|
$skip: skip,
|
|
disabled: false
|
|
}
|
|
});
|
|
total = partialResponse.total;
|
|
result === undefined ? result = partialResponse : partialResponse.data.map(this.fillResults(result));
|
|
skip = skip + limit;
|
|
}
|
|
|
|
// Add location and maps button
|
|
result.data.forEach(function(ambassador){
|
|
ambassador.location = {
|
|
searchText: app.addLocationSearchText(ambassador.cities),
|
|
value: app.addLocation(ambassador.cities)
|
|
}
|
|
ambassador.telegram_original = ambassador.telegram;
|
|
ambassador.telegram = {
|
|
searchText: ambassador.telegram,
|
|
value: (
|
|
<a
|
|
href={`https://t.me/${(ambassador.telegram.trim().charAt(0) === '@') ?
|
|
ambassador.telegram.trim().slice(1): ambassador.telegram.trim()}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>{ambassador.telegram}</a>
|
|
)
|
|
};
|
|
ambassador.map = app.addMapButton(ambassador, ambassador.cities);
|
|
ambassador.link = {
|
|
searchText: stripProtocol(ambassador.url),
|
|
value: (
|
|
<a target="_blank" rel="noopener noreferrer"
|
|
href={ambassador.url}>{stripProtocol(ambassador.url)}</a>
|
|
)
|
|
};
|
|
});
|
|
|
|
// Once both return, update the state
|
|
app.setState({
|
|
loading: false,
|
|
ambassadors: result,
|
|
ambassadorsSearch: result.data
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @description Get merchants from the web service
|
|
* @param {number} [limit=10] - Max items to be returned.
|
|
* @param {number} [skip=0] - Start index search
|
|
*/
|
|
getMerchants = async (limit = 50, skip = 0) => {
|
|
const app = this;
|
|
// Initially we don't know how much the total value is, so to make sure we enter the loop
|
|
// at least once we're just setting it to be 1
|
|
let total = 1;
|
|
|
|
const merchants = Client.service('api/v1/merchants');
|
|
|
|
let result;
|
|
while(skip < total){
|
|
let partialResponse = await merchants.find({
|
|
query: {
|
|
$sort: { account: 1 },
|
|
$limit: limit,
|
|
$skip: skip
|
|
}
|
|
});
|
|
total = partialResponse.total;
|
|
result === undefined ? result = partialResponse : partialResponse.data.map(this.fillResults(result));
|
|
skip = skip + limit;
|
|
}
|
|
|
|
result.data.forEach(function(merchants){
|
|
if(merchants.city !== undefined) merchants.city = (merchants.city).replace(/(^|\s)\S/g, l => l.toUpperCase());
|
|
if(merchants.country !== undefined) merchants.country = countries.getName(merchants.country);
|
|
});
|
|
|
|
const markers = result.data.map(merchant => {
|
|
const infoDescription = <div>
|
|
<div><b>Address</b>: {merchant.address}</div>
|
|
{(merchant.phone) && (<div><b>Phone</b>: {merchant.phone}</div>)}
|
|
</div>;
|
|
const marker = {
|
|
lat: merchant.lat,
|
|
lng: merchant.lon,
|
|
withInfo: true,
|
|
infoTitle: merchant.name,
|
|
infoDescription: infoDescription,
|
|
};
|
|
return marker;
|
|
});
|
|
|
|
// Once both return, update the state
|
|
app.setState({
|
|
merchantMarkers: markers
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @description Close Maps modal.
|
|
*/
|
|
closeMapsModal() {
|
|
this.setState({
|
|
mapsLat: 0,
|
|
mapsLon: 0,
|
|
mapsModalIsOpen: false
|
|
});
|
|
}
|
|
|
|
openMaps(name, address, lat, lon){
|
|
this.setState({
|
|
mapsTitle: name,
|
|
mapsDescription: address,
|
|
mapsLat: lat,
|
|
mapsLon: lon,
|
|
mapsModalIsOpen: true
|
|
});
|
|
}
|
|
|
|
addLocationSearchText(cities){
|
|
let searchText = '';
|
|
cities.forEach((location) => {
|
|
searchText += `${countries.getName(location.country)} - ${(location.name).replace(/(^|\s)\S/g, l => l.toUpperCase())}`;
|
|
});
|
|
|
|
return searchText;
|
|
}
|
|
|
|
addLocation(cities){
|
|
return (
|
|
<span>
|
|
{(cities.length > 1) && (<span><br /></span>)}
|
|
{cities.map((location, index) => (
|
|
<span key={index}>
|
|
{`${(location.name).replace(/(^|\s)\S/g, l => l.toUpperCase())} - ${countries.getName(location.country)}`}
|
|
{(cities.length > 1) && (<span><br /><br /></span>)}
|
|
</span>
|
|
))}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
addMapButton(ambassador, cities){
|
|
const app = this;
|
|
return (
|
|
<span>
|
|
{cities.map((location, index) => {
|
|
const infoDescription = <div>
|
|
<div><b>Location</b>: {(location.name).replace(/(^|\s)\S/g, l => l.toUpperCase())} - {countries.getName(location.country)}</div>
|
|
{(ambassador.nickname) && (<div><b>Nickname</b>: {ambassador.nickname}</div>)}
|
|
{(ambassador.telegram_original) && (<div><b>Telegram</b>:
|
|
<a
|
|
href={`https://t.me/${(ambassador.telegram_original.trim().charAt(0) === '@') ? ambassador.telegram_original.trim().slice(1): ambassador.telegram_original.trim()}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>{ambassador.telegram_original}</a>
|
|
</div>)}
|
|
{(ambassador.keybase) && (<div><b>Keybase</b>: {ambassador.keybase}</div>)}
|
|
{(ambassador.email) && (<div><b>Email</b>: {ambassador.email}</div>)}
|
|
{(ambassador.phone) && (<div><b>Phone</b>: {ambassador.phone}</div>)}
|
|
{(ambassador.url) && (<div><b>URL:</b>: <a target="_blank" rel="noopener noreferrer"
|
|
href={ambassador.url}>{stripProtocol(ambassador.url)}</a></div>)}
|
|
</div>;
|
|
return (
|
|
<div key={index}>
|
|
<Button
|
|
className="App-button"
|
|
variant="contained"
|
|
style={{
|
|
backgroundColor: "#2069b3",
|
|
color: 'white',
|
|
marginTop: 5,
|
|
marginBottom: 5
|
|
}}
|
|
onClick={() => app.openMaps(
|
|
ambassador.nickname,
|
|
infoDescription,
|
|
location.lat,
|
|
location.lon
|
|
)}
|
|
>Show on Map
|
|
</Button>
|
|
</div>
|
|
);})}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
handleSearchChange(data){
|
|
this.setState({ ambassadorsSearch: data });
|
|
}
|
|
|
|
render() {
|
|
let { data } = this.state.ambassadors;
|
|
const { ambassadorsSearch, merchantMarkers } = this.state;
|
|
|
|
const ambassadorsMarkers = [];
|
|
ambassadorsSearch.forEach(ambassador => {
|
|
ambassador.cities.forEach(function(city) {
|
|
const infoDescription = <div>
|
|
<div><b>Location</b>: {(city.name).replace(/(^|\s)\S/g, l => l.toUpperCase())} - {countries.getName(city.country)}</div>
|
|
{(ambassador.nickname) && (<div><b>Nickname</b>: {ambassador.nickname}</div>)}
|
|
{(ambassador.telegram_original) && (<div><b>Telegram</b>: <a
|
|
href={`https://t.me/${(ambassador.telegram_original.trim().charAt(0) === '@') ? ambassador.telegram_original.trim().slice(1): ambassador.telegram_original.trim()}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>{ambassador.telegram_original}</a></div>)}
|
|
{(ambassador.keybase) && (<div><b>Keybase</b>: {ambassador.keybase}</div>)}
|
|
{(ambassador.email) && (<div><b>Email</b>: {ambassador.email}</div>)}
|
|
{(ambassador.phone) && (<div><b>Phone</b>: {ambassador.phone}</div>)}
|
|
{(ambassador.url) && (<div><b>URL:</b>: <a target="_blank" rel="noopener noreferrer"
|
|
href={ambassador.url}>{stripProtocol(ambassador.url)}</a></div>)}
|
|
</div>;
|
|
const marker = {
|
|
lat: city.lat,
|
|
lng: city.lon,
|
|
withInfo: true,
|
|
infoTitle: ambassador.nickname,
|
|
infoDescription: infoDescription,
|
|
};
|
|
ambassadorsMarkers.push(marker);
|
|
});
|
|
});
|
|
|
|
data = data.sort(sortBy('location.searchText'));
|
|
|
|
const textComponent = (
|
|
<span>
|
|
<FormattedHTMLMessage id="ambassadors.description1" />
|
|
<Link to="/merchants">
|
|
<FormattedMessage id="ambassadors.merchants_link_description" />
|
|
</Link>
|
|
<FormattedHTMLMessage id="ambassadors.description2" />
|
|
</span>
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<AppHeader />
|
|
|
|
<div id="maincontent">
|
|
<section data-spy="scroll" data-target="#mainNav" id="services">
|
|
<div className="containerfix">
|
|
<div className="row">
|
|
<div className="col-md-10 mx-md-auto">
|
|
|
|
<h2 className="ambassadorsTitle" style={centerStyle}><FormattedMessage id="ambassadors.title" /></h2>
|
|
{ /* Conditional Rendering */}
|
|
{(this.state.loading) ? (
|
|
<img src={LoadingGif} alt="Loading" style={loadingStyle} />
|
|
): (
|
|
<div>
|
|
<Modal
|
|
isOpen={this.state.mapsModalIsOpen}
|
|
onRequestClose={() => this.closeMapsModal()}
|
|
style={mapsStyles}
|
|
ariaHideApp={false}
|
|
contentLabel={this.state.mapsTitle}
|
|
>
|
|
<PreviewMap
|
|
icon={AmbassadorPin}
|
|
infoTitle={this.state.mapsTitle}
|
|
infoDescription={this.state.mapsDescription}
|
|
lat={this.state.mapsLat}
|
|
lng={this.state.mapsLon}
|
|
width="800px"
|
|
height="600px"
|
|
/>
|
|
</Modal>
|
|
{(data.length > 0) ? (
|
|
<div>
|
|
<br />
|
|
<EnhancedTable
|
|
description={textComponent}
|
|
columnData={columnData}
|
|
data={data}
|
|
orderBy="Location"
|
|
showSearchColumns={false}
|
|
rowsPerPage={10}
|
|
isAdmin={false}
|
|
onSearchChange={(data) => this.handleSearchChange(data)}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div style={centerStyle}>No Data found</div>
|
|
)}
|
|
<div className="map">
|
|
<LayerMap
|
|
ambassadors={ambassadorsMarkers}
|
|
merchants={merchantMarkers}
|
|
ambassadorsLayer={true}
|
|
merchantsLayer={false}
|
|
mapHeight={'600px'}
|
|
ambsMap={true}
|
|
showControls={this.state.mapsModalIsOpen}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export { AmbassadorsPage };
|