diff --git a/client/js/common/google-api.js b/client/js/common/google-api.js deleted file mode 100644 index 077116e7..00000000 --- a/client/js/common/google-api.js +++ /dev/null @@ -1,49 +0,0 @@ -// License: LGPL-3.0-or-later -const flyd = require('flyd') -const filter = require('flyd/module/filter') -const R = require('ramda') - -var googz = {} - -// loads client API and returns a true stream if user is already signed in -googz.init = scope => { - if(document.getElementById('googzAuthApi')) return - var script = document.createElement('script') - script.type = 'text/javascript' - script.id = 'googzAuthApi' - document.body.appendChild(script) - script.src = 'https://apis.google.com/js/api.js?onload=loadGoogleClientLib' - - const isSignedIn$ = flyd.stream() - - window.loadGoogleClientLib = _ => { - gapi.load('client:auth2', _ => { - gapi.client.setApiKey(app.google_api) - gapi.auth2.init( - {'client_id': app.google_auth_client_id - , 'scope': scope} - ).then(_ => { - isSignedIn$(gapi.auth2.getAuthInstance().isSignedIn.get()) - }) - }) - } - return isSignedIn$ -} - -// returns a stream that will have a value once the user has signed in -googz.signIn = _ => { - const isSigningIn$ = flyd.stream() - gapi.auth2.getAuthInstance().signIn().then(isSigningIn$) - return isSigningIn$ -} - -// returns a stream with the email of the signed in user -googz.email = _ => { - const email$ = flyd.stream() - var profile = gapi.auth2.getAuthInstance() - var email = profile.currentUser.get().getBasicProfile().getEmail() - return email$(email) -} - -module.exports = googz - diff --git a/client/js/nonprofits/supporters/index/sidepanel/generate-content.js b/client/js/nonprofits/supporters/index/sidepanel/generate-content.js index 6d740c2c..b392c127 100644 --- a/client/js/nonprofits/supporters/index/sidepanel/generate-content.js +++ b/client/js/nonprofits/supporters/index/sidepanel/generate-content.js @@ -100,16 +100,15 @@ exports.SupporterNote = (data, state) => { exports.SupporterEmail = (data, state) => { var jd = data.json_data - var canView = jd.from === state.gmail.from$() + var canView = false var body = [h('span', `Subject: ${jd.subject}`), h('br')] var thread = h('a', {props: {href: '#'}, on: {click: [state.threadId$, jd.gmail_thread_id]}}, 'View thread') - var signIn = h('small', [ - h('a', {props: {href: '#'}, on: {click: state.gmail.newSignIn$}}, 'Sign in') - , ` as ${jd.from} to view thread`]) + return { title: `Email thread started by ${jd.from}` , icon: 'fa-envelope' - , body: canView ? R.concat(body, thread) : R.concat(body, signIn) + , body: body + // , body: canView ? R.concat(body, thread) : R.concat(body, signIn) } } diff --git a/client/js/nonprofits/supporters/index/sidepanel/gmail/compose-modal.js b/client/js/nonprofits/supporters/index/sidepanel/gmail/compose-modal.js deleted file mode 100644 index 8894f011..00000000 --- a/client/js/nonprofits/supporters/index/sidepanel/gmail/compose-modal.js +++ /dev/null @@ -1,55 +0,0 @@ -// License: LGPL-3.0-or-later -const modal = require('ff-core/modal') -const h = require('snabbdom/h') -const validated = require('ff-core/validated-form') -const button = require('ff-core/button') -const R = require('ramda') - -module.exports = state => - modal({ - title: h('h4.modalTitle', 'Email') - , id$: state.modalID$ - , thisID: 'composeGmailModal' - , body: body(state) - }) - -const fieldset = (name, placeholder, field) => - h('fieldset.group.u-marginTop--10', [ - h('label.u-inlineBlock.col-2.u-paddingTop--5', utils.capitalize(name) + ': ') - , h('div.col-right-10.u-margin--0', [ - field(h('textarea.u-margin--0', {props: {rows: '1', name, placeholder}})) - ]) - ]) - -const group = (labelText, spanContent) => - h('div.group.u-marginBottom--10',[ - h('label.u-inlineBlock.col-2', `${labelText}: `) - , h('span.col-right--10', [spanContent]) - ]) - -const body = state => { - var gmail = state.gmail - var to = state.supporter$().email - var from = gmail.from$() - var field = validated.field(gmail.composeForm) - return validated.form(gmail.composeForm, h('form', [ - group('From' - , h('span', [ - gmail.from$() + '' - , h('a.u-small.u-paddingLeft--10', {props: {href: '#'}, on: {click: gmail.newSignIn$}} - , '(Switch account)') - ]) - ) - , group('To', to + '') - , h('input', {props: {type: 'hidden', name: 'from', value: gmail.from$()}}) - , field(h('input', {props: {type: 'hidden', name: 'to', value: to}})) - , fieldset('cc', 'Cc (separate with commas)', field) - , fieldset('bcc', 'Bcc (separate with commas)', field) - , fieldset('subject', 'Subject (required)', field) - , field(h('textarea.u-marginTop--10', {props: {name: 'body', placeholder: 'Message', rows: '10'}})) - , h('div.u-marginTop--20.u-centered', [ - button({buttonText: 'Send', loadingText: 'Sending...', loading$: state.loading$, error: state.error$}) - ]) - ])) -} - diff --git a/client/js/nonprofits/supporters/index/sidepanel/gmail/format.js b/client/js/nonprofits/supporters/index/sidepanel/gmail/format.js deleted file mode 100644 index 7c02664a..00000000 --- a/client/js/nonprofits/supporters/index/sidepanel/gmail/format.js +++ /dev/null @@ -1,70 +0,0 @@ -// License: LGPL-3.0-or-later -const R = require('ramda') -const moment = require('moment') - -const b64 = require('../../../../../components/b64') -const encode = require( '../../../../../components/encode-plain-email') - -const format = {} - -format.composeData = data => { - const cleanArray = s => s.replace(/ /g, '').split(',') - data.cc = data.cc ? cleanArray(data.cc) : false - data.bcc = data.bcc ? cleanArray(data.bcc) : false - return {'userId': 'me', 'resource': {'raw': encode(data)}} -} - -format.replyData = data => { - return {'userId': 'me', 'resource': {'raw': encode(data), 'threadId': data.threadId}} -} - -format.saveData = (resp, formData) => { - var data = R.omit(['cc', 'bcc'], formData) - return R.merge(data, { - supporter_id: appl.supporter_details.id - , nonprofit_id: app.nonprofit_id - , recipient_count: '1' - , gmail_thread_id: resp.threadId - } - ) -} - -format.thread = r => { - const value = (s, o) => { - var obj = R.find(R.propEq('name', s), o) - if (!obj) return false - return obj.value - } - - const cleanReply = s => { - // remove all the replies within the message - var reply = R.take(1, s.split(/\n> /)).join('\n').trim() - // remove signature - return R.dropLast(1, reply.split(/\n/)).join('\n').trim() - } - - var firstHeader = r.messages[0].payload.headers - - var from = value('From', firstHeader) - - var thread = {subject: value('Subject', firstHeader)} - - thread.messages = R.map(m => { - var headers = m.payload.headers - return { - from: value('From', headers) - , to: value('To', headers) - , cc: value('Cc', headers) - , bcc: value('Bcc', headers) - , date: moment(value('Date', headers)).format("ddd, M/D/YYYY, h:mmA") - , body: m.payload.parts - ? cleanReply(b64.decode(R.find(R.propEq('mimeType', 'text/plain'), m.payload.parts).body.data)) - : b64.decode(m.payload.body.data) - } - }, r.messages) - - return thread -} - -module.exports = format - diff --git a/client/js/nonprofits/supporters/index/sidepanel/gmail/index.js b/client/js/nonprofits/supporters/index/sidepanel/gmail/index.js deleted file mode 100644 index 8cd8da67..00000000 --- a/client/js/nonprofits/supporters/index/sidepanel/gmail/index.js +++ /dev/null @@ -1,117 +0,0 @@ -// License: LGPL-3.0-or-later -// npm -const R = require('ramda') -const serialize = require('form-serialize') -const flyd = require('flyd') -const mergeAll = require('flyd/module/mergeall') -const filter = require('flyd/module/filter') -const keepWhen = require('flyd/module/keepwhen') -const takeUntil = require('flyd/module/takeuntil') -const sampleOn = require('flyd/module/sampleon') -const zip = require('flyd-zip') -const url$ = require('flyd-url') -const snabbdom = require('snabbdom') -const h = require('snabbdom/h') -const render = require('ff-core/render') -const validated = require('ff-core/validated-form') -const notification = require('ff-core/notification') - -const flatMap = R.curry(require('flyd/module/flatmap')) - -// common -const request = require('../../../../../common/request') -const googz = require('../../../../../common/google-api') - -// from gmail dir -const format = require('./format') - -var constraints = { - to: {email: true, required: true} -, cc: {email: true} -, bcc: {email: true} -, subject: {required: true} -, body: {required: true} -} - -function init(parentState) { - var state = {} - - const isSignedInOnLoad$ = googz.init('https://mail.google.com/') - - state.newSignIn$ = flyd.stream() - - const signInClick$ = R.compose( - filter(R.not) - , sampleOn(R.__, isSignedInOnLoad$) - )(parentState.clickComposing$) - - const isSignedInWithClick$ = flatMap(googz.signIn, - flyd.merge(state.newSignIn$, signInClick$)) - - const isSignedIn$ = flyd.merge(isSignedInOnLoad$, isSignedInWithClick$) - - flyd.map(loadGmailApi, isSignedIn$) - - state.from$ = R.compose( - flatMap(googz.email) - , filter(Boolean) - )(isSignedIn$) - - const threadData$ = flatMap(getThreadData, parentState.threadId$) - - state.formattedThreadData$ = flyd.map(format.thread, threadData$) - - state.composeForm = validated.init({constraints}) - - state.replyForm = validated.init({constraints: {body: {required: true}}}) - - const formattedComposeData$ = flyd.map(format.composeData, state.composeForm.validData$) - - const formattedReplyData$ = flyd.map(format.replyData, state.replyForm.validData$) - - const sendData$ = flyd.merge(formattedReplyData$, formattedComposeData$) - - state.sendResponse$ = flatMap(send, sendData$) - - const formattedSaveData$ = flyd.map(R.apply(format.saveData), zip([state.sendResponse$, state.composeForm.validData$])) - - state.saveResult$ = flatMap(save, formattedSaveData$) - - const afterCompose$ = sampleOn(state.sendResponse$, state.composeForm.submit$) - - const afterReply$ = sampleOn(state.sendResponse$, state.replyForm.submit$) - - flyd.map(_ => state.composeForm.submit$().reset(), afterCompose$) - flyd.map(_ => state.replyForm.submit$().reset(), afterReply$) - - return state -} - -const send = data => { - const result$ = flyd.stream() - gapi.client.gmail.users.messages.send(data).execute(result$) - return result$ -} - -const loadGmailApi = _ => { - if(gapi.client.gmail) return - gapi.client.load('gmail', 'v1') -} - -const save = data => { - const path = `/nonprofits/${app.nonprofit_id}/supporter_emails/gmail` - var send = {gmail: data} - return flyd.map(req => req.body, request({method: 'post', path, send}).load) -} - -const getThreadData = id => { - const result$ = flyd.stream() - gapi.client.gmail.users.threads.get({ - 'userId': 'me' - , 'id': id - }).execute(result$) - return result$ -} - -module.exports = {init} - diff --git a/client/js/nonprofits/supporters/index/sidepanel/gmail/reply-modal.js b/client/js/nonprofits/supporters/index/sidepanel/gmail/reply-modal.js deleted file mode 100644 index 48ea6dcc..00000000 --- a/client/js/nonprofits/supporters/index/sidepanel/gmail/reply-modal.js +++ /dev/null @@ -1,64 +0,0 @@ -// License: LGPL-3.0-or-later -const modal = require('ff-core/modal') -const h = require('snabbdom/h') -const validated = require('ff-core/validated-form') -const button = require('ff-core/button') -const R = require('ramda') - -module.exports = state => { - if(!state.gmail.formattedThreadData$()) return '' - return modal({ - title: h('h4.modalTitle', state.gmail.formattedThreadData$().subject) - , id$: state.modalID$ - , thisID: 'replyGmailModal' - , body: body(state) - }) -} - -const group = (labelText, spanText) => - h('div.group.u-marginBottom--5',[ - h('label.u-inlineBlock.col-2', `${labelText}: `) - , h('span.col-right--10', spanText + '') - ]) - -const body = state => - h('div', [ - h('div', R.map(message, state.gmail.formattedThreadData$().messages)) - , form(state) - ]) - -const message = m => - h('section.pastelBox--grey.u-padding--10.u-marginBottom--20.u-medPs', [ - h('table.u-width--full', [ - h('tr', [ - h('td', [h('small.u-strong', m.from)]) - , h('td', [h('small.u-textAlign--right.u-block', m.date)]) - ]) - ]) - , h('div', R.map(greyP, R.filter(x => x.email, - [{label: 'to', email: m.to}, {label: 'cc', email: m.cc}, {label: 'bcc', email: m.bcc}])) - ) - , h('p.u-marginTop--20', {style: {whiteSpace: 'pre-wrap'}}, m.body) - ]) - -const greyP = x => h('p.u-margin--0', [h('small.u-color--grey', `${x.label} ${x.email}`)]) - -const form = state => { - var gmail = state.gmail - var to = state.supporter$().email - var from = gmail.from$() - var field = validated.field(gmail.replyForm) - return validated.form(gmail.replyForm, h('form.u-marginTop--30', [ - group('From', from) - , group('To', to) - , h('input', {props: {type: 'hidden', name: 'from', value: from}}) - , h('input', {props: {type: 'hidden', name: 'to', value: to}}) - , h('input', {props: {type: 'hidden', name: 'subject', value: gmail.formattedThreadData$().subject}}) - , h('input', {props: {type: 'hidden', name: 'threadId', value: state.threadId$()}}) - , field(h('textarea.u-marginTop--10', {props: {name: 'body', placeholder: 'Reply', rows: '10'}})) - , h('div.u-marginTop--20.u-centered', [ - button({buttonText: 'Send reply', loadingText: 'Sending reply...', loading$: state.loading$}) - ]) - ])) -} - diff --git a/client/js/nonprofits/supporters/index/sidepanel/index.js b/client/js/nonprofits/supporters/index/sidepanel/index.js index 19e97962..6dc50841 100644 --- a/client/js/nonprofits/supporters/index/sidepanel/index.js +++ b/client/js/nonprofits/supporters/index/sidepanel/index.js @@ -14,11 +14,8 @@ const notification = require('ff-core/notification') const request = require('../../../../common/request') const confirm = require('../../../../components/confirmation-modal') -const gmail = require('./gmail') const actions = require('./supporter-actions') const activities = require('./supporter-activities') -const replyModal = require('./gmail/reply-modal') -const composeModal = require('./gmail/compose-modal') const offsiteDonationForm = require('./offsite-donation-form') const supporterNoteForm = require('./supporter-note-form') @@ -51,9 +48,6 @@ const init = _ => { state.supporter$ = flyd.merge(supporterResp$, flyd.stream({})) - state.composeOrReply$ = flyd.merge(state.clickComposing$, state.threadId$) - - state.gmail = gmail.init(state) state.offsiteDonationForm = offsiteDonationForm.init(state) @@ -77,7 +71,6 @@ const init = _ => { // All streams that we want to trigger a refresh of the supporter timeline const fetchActivitiesWith$ = mergeAll([ state.pathPrefix$ - , state.gmail.saveResult$ , state.offsiteDonationForm.saved$ , state.supporterNoteForm.saved$ , deleteNoteResp$ @@ -92,22 +85,12 @@ const init = _ => { state.activities = activities.init(state) state.modalID$ = mergeAll([ - flyd.map(()=> 'composeGmailModal', state.clickComposing$) - , flyd.map(()=> 'replyGmailModal', state.threadId$) , flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$) - , flyd.map(()=> null, state.gmail.sendResponse$) , flyd.map(()=> null, state.supporterNoteForm.saved$) ]) - state.loading$ = mergeAll([ - flyd.map(R.always(true), state.gmail.composeForm.validData$) - , flyd.map(R.always(true), state.gmail.replyForm.validData$) - , flyd.map(R.always(false), state.gmail.sendResponse$) - ]) - const message$ = mergeAll([ - flyd.map(()=> 'Email sent', state.gmail.sendResponse$) , flyd.map(()=> 'Successfully created a new offsite contribution', state.offsiteDonationForm.saved$) , flyd.map(()=> `Successfully ${noteMsg(state.noteAjaxMethod$)} supporter note`, state.supporterNoteForm.saved$) , flyd.map(()=> 'Successfully deleted supporter note', deleteNoteResp$) @@ -141,11 +124,9 @@ const view = state => { return h('div', [ actions.view(state) , activities.view(state) - , composeModal(state) , notification.view(state.notification) , offsiteDonationForm.view(R.merge(state.offsiteDonationForm)) , supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$})) - , replyModal(state) , confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?') ]) } diff --git a/client/js/nonprofits/supporters/index/sidepanel/supporter-actions.js b/client/js/nonprofits/supporters/index/sidepanel/supporter-actions.js index 6fd4a969..304d0ba3 100644 --- a/client/js/nonprofits/supporters/index/sidepanel/supporter-actions.js +++ b/client/js/nonprofits/supporters/index/sidepanel/supporter-actions.js @@ -13,7 +13,7 @@ const button = (text, stream) => const view = state => h('section.timeline-actions.u-padding--10', [ button('Note', state.newNote$) - , button('Email', state.clickComposing$) + , button('Email', () => { window.open(`mailto:${state.supporter$().email}`)}) , button('Donation', () => appl.open_donation_modal(state.supporter$().id, () => {state.offsiteDonationForm.saved$(Math.random())} )