import React from 'react'; import { localized, IdentityStore, Contact, FeatureUsageStore, PropTypes, DOMUtils, RegExpUtils, Thread, Utils, } from 'mailspring-exports'; import { RetinaImg } from 'mailspring-component-kit'; import crypto from 'crypto'; import moment from 'moment-timezone'; import ParticipantProfileDataSource from './participant-profile-data-source'; class TimeInTimezone extends React.Component<{ timeZone: string }, { tick: number }> { constructor(props) { super(props); this.state = { tick: 0 }; } _timer: NodeJS.Timer; componentDidMount() { this.scheduleTick(); } componentWillUnmount() { if (this._timer) clearInterval(this._timer); } scheduleTick = () => { // schedules for the next minute change each minute this._timer = setTimeout(() => { this.setState({ tick: this.state.tick + 1 }, this.scheduleTick); }, 60000 - Date.now() % 60000); }; render() { return ( {`Currently ${moment() .tz(this.props.timeZone) .format('h:mma')}`} ); } } class ProfilePictureOrColorBox extends React.Component<{ contact: Contact; loading: boolean; avatar: string; }> { static propTypes = { loading: PropTypes.bool, contact: PropTypes.object, profilePicture: PropTypes.string, }; render() { const { contact, loading, avatar } = this.props; const hue = Utils.hueForString(contact.email); const bgColor = `hsl(${hue}, 50%, 45%)`; const hash = crypto .createHash('md5') .update((contact.email || '').toLowerCase().trim()) .digest('hex'); const gravatarBg = `url("https://www.gravatar.com/avatar/${hash}/?s=88&msw=88&msh=88&d=blank")`; let content = (
{contact.nameAbbreviation()}
); if (loading) { content = (
); } if (avatar) { content = Profile; } return (
{content}
); } } class SocialProfileLink extends React.Component<{ handle: string; service: string }> { static propTypes = { service: PropTypes.string, handle: PropTypes.string, }; render() { const { handle, service } = this.props; if (!handle) { return false; } return ( ); } } class TextBlockWithAutolinkedElements extends React.Component<{ text: string; className: string }> { static propTypes = { className: PropTypes.string, text: PropTypes.string, }; render() { if (!this.props.text) { return false; } const nodes = []; const hashtagOrMentionRegex = RegExpUtils.hashtagOrMentionRegex(); let remainder = this.props.text; let match = null; let count = 0; while ((match = hashtagOrMentionRegex.exec(remainder))) { // the first char of the match is whitespace, match[1] is # or @, match[2] is the tag itself. nodes.push(remainder.substr(0, match.index + 1)); if (match[1] === '#') { nodes.push( {`#${match[2]}`} ); } if (match[1] === '@') { nodes.push({`@${match[2]}`}); } remainder = remainder.substr(match.index + match[0].length); count += 1; } nodes.push(remainder); return

{nodes}

; } } class IconRow extends React.Component<{ node: React.ReactChild; icon: string }> { static propTypes = { node: PropTypes.node, icon: PropTypes.string, }; render() { const { node, icon } = this.props; if (!node) { return false; } return (
{node}
); } } class LocationRow extends React.Component<{ location: string }> { static propTypes = { location: PropTypes.string, }; render() { return ( {this.props.location} {' ['} View {']'} ) } /> ); } } interface SidebarParticipantProfileProps { contact: Contact; contactThreads: Thread[]; } interface SidebarParticipantProfileState { trialing: boolean; loading: boolean; loaded: boolean; avatar?: string; company?: ICompany; person?: IPerson; } interface ICompany { name: string; domain: string; category?: { industry?: string; sector?: string; }; description: string; location: string; timeZone: string; logo: string; facebook?: { handle: string }; twitter?: { handle: string }; linkedin?: { handle: string }; crunchbase?: { handle: string }; type: string; ticker: string; phone: string; metrics: { raised?: string; marketCap?: string; employees?: string; employeesRange?: string; }; } interface IPerson { facebook?: { handle: string }; twitter?: { handle: string }; linkedin?: { handle: string }; employment?: { title: string; name: string; }; location?: string; bio?: string; } export default class SidebarParticipantProfile extends React.Component< SidebarParticipantProfileProps, SidebarParticipantProfileState > { static displayName = 'SidebarParticipantProfile'; static propTypes = { contact: PropTypes.object, contactThreads: PropTypes.array, }; static containerStyles = { order: 0, }; _mounted: boolean = false; state: SidebarParticipantProfileState = { trialing: !IdentityStore.hasProFeatures(), loading: IdentityStore.hasProFeatures(), loaded: false, }; constructor(props) { super(props); const contactState = ParticipantProfileDataSource.getCache(props.contact.email); if (contactState) { this.state = Object.assign(this.state, { loaded: true }, contactState); } } componentDidMount() { this._mounted = true; if (this.state.loading) { // Wait until we know they've "settled" on this email to reduce the number of // requests to the contact search endpoint. setTimeout(this._onFindContact, 2000); } } componentWillUnmount() { this._mounted = false; } _onClickedToTry = async () => { try { await FeatureUsageStore.markUsedOrUpgrade('contact-profiles', { headerText: localized('All Contact Previews Used'), rechargeText: `${localized( `You can view contact profiles for %1$@ emails each %2$@ with Mailspring Basic.` )} ${localized('Upgrade to Pro today!')}`, iconUrl: 'mailspring://participant-profile/assets/ic-contact-profile-modal@2x.png', }); } catch (err) { // user does not have access to this feature return; } this._onFindContact(); }; _onFindContact = async () => { if (!this._mounted) { return; } if (!this.state.loading) { this.setState({ loading: true }); } ParticipantProfileDataSource.find(this.props.contact).then(result => { if (!this._mounted) { return; } this.setState(Object.assign({ loading: false, loaded: true }, result)); }); }; _onSelect = event => { const el = event.target; const sel = document.getSelection(); if (el.contains(sel.anchorNode) && !sel.isCollapsed) { return; } const anchor = DOMUtils.findFirstTextNode(el); const focus = DOMUtils.findLastTextNode(el); if (anchor && focus && focus.data) { sel.setBaseAndExtent(anchor, 0, focus, focus.data.length); } }; _renderFindCTA() { if (!this.state.trialing || this.state.loaded) { return; } if (!this.props.contact.email || Utils.likelyNonHumanEmail(this.props.contact.email)) { return; } return (

{localized( `The contact sidebar in Mailspring Pro shows information about the people and companies you're emailing with.` )}

{!this.state.loading ? localized(`Try it Now`) : localized(`Loading...`)}
); } _renderCompanyInfo() { if (!this.state.company || !this.state.company.name) { return; } const { name, domain, category, description, location, timeZone, logo, facebook, twitter, linkedin, crunchbase, type, ticker, phone, metrics, } = this.state.company; let employees = null; let funding = null; if (metrics) { if (metrics.raised) { funding = `${localized(`Raised`)} ${(Number(metrics.raised) || 0).toLocaleString()}`; } else if (metrics.marketCap) { funding = `${localized(`Market Cap`)} $${( Number(metrics.marketCap) || 0 ).toLocaleString()}`; } if (metrics.employees) { employees = `${(Number(metrics.employees) || 0).toLocaleString()} ${localized( `employees` )}`; } else if (metrics.employeesRange) { employees = `${metrics.employeesRange} ${localized(`employees`)}`; } } return (
{logo && ( )}
{name}
{domain && ( {domain} )}
{`${timeZone.replace('_', ' ')} - `} ) } />
); } _renderPersonInfo() { const { facebook, linkedin, twitter, employment, location, bio } = this.state.person || ({} as IPerson); return (
{this.props.contact.fullName() !== this.props.contact.email && (
{this.props.contact.fullName()}
)} {employment && (
{employment.title && {employment.title}, } {employment.name}
)}
{this.props.contact.email}
); } render() { return (
{this._renderPersonInfo()} {this._renderCompanyInfo()} {this._renderFindCTA()}
); } }