Email Diagram




Read / Fetch



Constant

  • File: app\containers\SampleFullstackApps\Email\reducers\emailConstants.js
  • Define constant:
                      
    export const FETCH_EMAIL = 'FETCH_EMAIL';
    export const FETCH_EMAIL_SUCCESS = 'FETCH_EMAIL_SUCCESS';
    export const FETCH_EMAIL_FAILED = 'FETCH_EMAIL_FAILED';
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Email\reducers\emailActions.js
  • Put each constant in actions
                      
    /* Load */
    export const loadMailSuccess = payload => ({
      type: types.FETCH_EMAIL_SUCCESS,
      payload,
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Email\reducers\emailSagas.js
  • Call load task action in saga's function
                      
    function* read() {
      const channel = yield call(subscribe);
      while (true) {
        const action = yield take(channel);
        yield put(action);
      }
    }
    
    function* watchEmail() {
      while (true) {
        emailList.path = 'emails/';
        const job = yield fork(read);
    
        yield take([FETCH_EMAIL]);
        yield cancel(job);
      }
    }
                      
                    
  • Subscribe from models
                      
    import EmailModels from '../models';
    import Init from '../models/init';
    
    import { loadMailSuccess } from './emailActions';
    
    const emailList = new EmailModels({
      onLoad: loadMailSuccess,
    }, Init);
    
    function subscribe() {
      return eventChannel(emit => emailList.subscribe(emit));
    }
    
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Email\reducers\emailReducer.js
  • Put all payload action with constant FETCH_EMAIL_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case FETCH_EMAIL_SUCCESS:
      return state.withMutations((mutableState) => {
        const items = fromJS(action.payload);
        mutableState
          .set('inbox', new List(items))
          .set('loading', false);
      });
                      
                    

Container

  • File: \app\containers\SampleFullstackApps\Email\index.js
  • Load state data to container as prop
                      
    import { getVisibleMail } from './reducers/selectors';                  
    const { emailData } = this.props;
    
    const mapStateToProps = state => ({
      emailData: getVisibleMail(state.get(reducer)),
    });
    
    return {
      <EmailList
        emailData={emailData}
        openMail={openMail}
        filterPage={currentPage}
        keyword={keyword}
        moveTo={update}
        remove={remove}
        toggleStar={update}
        reply={this.handleReply}
        loading={loading}
      />
    }
                      
                    

Components

  • File: app\components\Email\EmailList.js
  • Populate emailData prop as dataArray to component
                      
    const getItem = dataArray => dataArray.map(data => {
      const renderHTML = { __html: mail.get('content') };
      if (mail.get('subject').toLowerCase().indexOf(keyword) === -1) {
        return false;
      }
      return (
        <ExpansionPanel className={classes.emailList} key={mail.get('key')} onChange={() => openMail(mail)}>
          <ExpansionPanelSummary className={classes.emailSummary} expandIcon={<ExpandMoreIcon />}>
            <div className={classes.fromHeading}>
              <Tooltip id="tooltip-mark" title={intl.formatMessage(messages.stared)}>
                <IconButton onClick={() => this.handleStared(mail)} className={classes.starBtn}>{mail.get('stared') ? (<Star className={classes.iconOrange} />) : (<StarBorder />) }</IconButton>
              </Tooltip>
              {mail.get('category') !== 'spam'
                ? (<Avatar alt="avatar" src={mail.get('avatar')} className={classes.avatar} />)
                : (<Avatar alt="avatar" className={classes.avatar}><ReportIcon /></Avatar>)
              }
              <Typography className={classes.heading}>
                {mail.get('category') === 'sent' && ('To ')}
                {mail.get('name')}
                <Typography variant="caption">{mail.get('date')}</Typography>
              </Typography>
            </div>
            <div className={classes.column}>
              <Typography className={classes.secondaryHeading} noWrap>{mail.get('subject')}</Typography>
              {getCategory(mail.get('category'))}
            </div>
          </ExpansionPanelSummary>
          <ExpansionPanelDetails className={classes.details}>
            <section>
              <div className={classes.topAction}>
                <Typography className={classes.headMail}>
                  {mail.get('category') !== 'sent' && (
                    <Fragment>
                      <FormattedMessage {...messages.from} />
                       
                      {mail.get('name')}
                       to me
                    </Fragment>
                  )}
                </Typography>
                <div className={classes.opt}>
                  <Tooltip id="tooltip-mark" title={intl.formatMessage(messages.stared)}>
                    <IconButton onClick={() => this.handleStared(mail)}>{mail.get('stared') ? (<Star className={classes.iconOrange} />) : (<StarBorder />) }</IconButton>
                  </Tooltip>
                  <Tooltip id="tooltip-mark" title={intl.formatMessage(messages.mark_to)}>
                    <IconButton
                      className={classes.button}
                      aria-label="mark"
                      aria-owns={anchorElOpt ? 'long-menu' : null}
                      aria-haspopup="true"
                      onClick={(event) => this.handleClickOpt(event, mail)}
                    >
                      <Bookmark />
                    </IconButton>
                  </Tooltip>
                  <Tooltip id="tooltip-mark" title={intl.formatMessage(messages.remove)}>
                    <IconButton className={classes.button} aria-label="Delete" onClick={() => remove(mail)}><Delete /></IconButton>
                  </Tooltip>
                </div>
              </div>
              <div className={classes.emailContent}>
                <Typography variant="h6" gutterBottom>{mail.get('subject')}</Typography>
                <article dangerouslySetInnerHTML={renderHTML} />
              </div>
            </section>
          </ExpansionPanelDetails>
          <Divider />
          <ExpansionPanelActions>
            <div className={classes.action}>
              <Button size="small">
                <FormattedMessage {...messages.forward} />
              </Button>
              <Button size="small" color="secondary" onClick={() => reply(mail)}>
                <FormattedMessage {...messages.reply} />
              </Button>
            </div>
          </ExpansionPanelActions>
        </ExpansionPanel>
      );
    });
                      
                    


Create (Send)



Constant

  • File: app\containers\SampleFullstackApps\Email\reducers\emailConstants.js
  • Define constant
                      
    export const SEND_MAIL = 'SEND_MAIL';
    export const SEND_MAIL_SUCCESS = 'SEND_MAIL_SUCCESS';
    export const SEND_MAIL_FAILED = 'SEND_MAIL_FAILED';                 
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Email\reducers\emailActions.js
  • Put each constant in actions
                      
    export const sendAction = payload => ({
      type: types.SEND_MAIL,
      payload,
    });
    
    export const sendActionSuccess = payload => ({
      type: types.SEND_MAIL_SUCCESS,
      payload,
    });
    
    export const sendActionFailed = error => ({
      type: types.SEND_MAIL_FAILED,
      payload: { error },
    });            
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Email\reducers\emailSagas.js
  • Define send models
                      
    const sendMail = write.bind(null, emailList, emailList.push, sendActionFailed);
                      
                    
  • Call send email action in saga's function
                      
    function* watchSendEmail() {
      while (true) {
        const { payload } = yield take(SEND_MAIL);
        yield fork(sendMail, payload);
      }
    }         
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Email\reducers\emailReducer.js
  • Put all payload action with constant SEND_MAIL_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case SEND_MAIL_SUCCESS:
      return state.withMutations((mutableState) => {
        const items = action.payload;
        mutableState.set('inbox', state.get('inbox').unshift(items));
        mutableState
          .set('selectedMailId', '')
          .set('openFrm', false)
          .set('notifMsg', notif.sent)
          .set('processing', false);
      });    
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Email\index.js
  • Dispatch send action in container
                      
    import { sendAction } from './reducers/emailActions';    
    
    const constDispatchToProps = dispatch => ({
      sendEmail: bindActionCreators(sendAction, dispatch),
    });
    
    return (
      <ComposeEmail
        to={to}
        subject={subject}
        compose={this.handleCompose}
        validMail={validMail}
        sendEmail={sendEmail}
        inputChange={this.handleChange}
        open={openFrm}
        closeForm={discard}
        processing={processing}
      />
    )
                      
                    

Component

  • File: app\components\Email\ComposeEmail.js
  • Send email values by onClick send button
                      
    handleSend = message => {
      const { sendEmail } = this.props;
      sendEmail(message);
      this.setState({ emailContent: '', files: [] });
    };
    
    return (
      <Button
        variant="contained"
        color="secondary"
        type="button"
        disabled={!to || !subject || processing}
        onClick={() => this.handleSend(newData)}
      >
        {processing && <CircularProgress size={24} className={classes.buttonProgress} />}
        <FormattedMessage {...messages.send} />
         
        <Send className={classes.sendIcon} />
      </Button>
    )
                      
                    


Update


Constant


  • File: app\containers\SampleFullstackApps\Email\reducers\emailConstants.js
  • Define constant
                      
    export const UPDATE_MAIL = 'UPDATE_MAIL';
    export const UPDATE_MAIL_SUCCESS = 'UPDATE_MAIL_SUCCESS';
    export const UPDATE_MAIL_FAILED = 'UPDATE_MAIL_FAILED';
                      
                    

  • File: app\containers\SampleFullstackApps\Email\reducers\emailActions.js
  • Put each constant in actions
                      
    export const updateAction = (mail, changes) => ({
      type: types.UPDATE_MAIL,
      payload: { mail, changes },
    });
    
    export const updateActionSuccess = payload => ({
      type: types.UPDATE_MAIL_SUCCESS,
      payload,
    });
    
    export const updateActionFailed = error => ({
      type: types.UPDATE_MAIL_SUCCESS,
      payload: { error },
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Email\reducers\emailSagas.js
  • Define update models
                      
    const updateMail = write.bind(null, emailList, emailList.update, updateActionFailed);
                      
                    
  • Call update email action in saga's function
                      
    function* watchUpdateEmail() {
      while (true) {
        const { payload } = yield take(UPDATE_MAIL);
        yield fork(updateMail, payload.mail.key, payload.changes);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Email\reducers\emailReducer.js
  • Put all payload action with constant UPDATE_MAIL_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case UPDATE_MAIL_SUCCESS:
      return state.withMutations((mutableState) => {
        mutableState
          .set(
            'inbox',
            state.get('inbox').map(mail => (mail.key === action.payload.key ? action.payload : mail))
          )
          .set('notifMsg', notif.updated);
      });
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Email\index.js
  • Dispatch update action in container.
                      
    import { updateAction } from './reducers/emailActions';
    
    const constDispatchToProps = dispatch => ({
      update: bindActionCreators(updateAction, dispatch),
    });
      
    return (
      <EmailList
        emailData={emailData}
        openMail={openMail}
        filterPage={currentPage}
        keyword={keyword}
        moveTo={update}
        remove={remove}
        toggleStar={update}
        reply={this.handleReply}
        loading={loading}
      />
    )
                      
                    

Component

  • File: app\components\Email\EmailList.js
  • Push updated Email attribute
                      
    handleMoveTo = (item, category) => {
        const { moveTo } = this.props;
        moveTo(item, { category });
        this.setState({ anchorElOpt: null });
      }
    
    handleStared = mail => {
      const { toggleStar } = this.props;
      toggleStar(mail, { stared: !mail.get('stared') });
    }
    
    return (
      <IconButton onClick={() => this.handleStared(mail)} className={classes.starBtn}>{mail.get('stared') ? (<Star className={classes.iconOrange} />) : (<StarBorder />) }</IconButton>
      
      <MenuItem selected onClick={() => this.handleMoveTo(itemToMove, 'updates')}>
        <Flag className={classes.iconOrange} />
         
        <FormattedMessage {...messages.updates} />
      </MenuItem>
    )
                      
                    


Delete


Constant

  • File: app\containers\SampleFullstackApps\Email\reducers\emailConstants.js
  • Define constant
                      
    export const DELETE_MAIL = 'DELETE_MAIL';
    export const DELETE_MAIL_SUCCESS = 'DELETE_MAIL_SUCCESS';
    export const DELETE_MAIL_FAILED = 'DELETE_MAIL_FAILED';
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Email\reducers\emailActions.js
  • Put each constant in actions
                      
    /* Delete */
    export const deleteAction = payload => ({
      type: types.DELETE_MAIL,
      payload,
    });
    
    export const deleteActionSuccess = payload => ({
      type: types.DELETE_MAIL_SUCCESS,
      payload,
    });
    
    export const deleteActionFailed = error => ({
      type: types.DELETE_MAIL_FAILED,
      payload: { error },
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Email\reducers\emailSagas.js
  • Define remove models
                      
    const removeMail = write.bind(null, emailList, emailList.remove, deleteActionFailed);
                      
                    
  • Call remove email action in saga's function
                      
    function* watchRemoveEmail() {
      while (true) {
        const { payload } = yield take(DELETE_MAIL);
        yield fork(removeMail, payload.key);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Email\reducers\emailReducer.js
  • Put all payload action with constant DELETE_MAIL_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case DELETE_MAIL_SUCCESS:
      return state.withMutations((mutableState) => {
        mutableState
          .set('inbox',
            state.get('inbox').filter(mail => mail.key !== action.payload.key)
          )
          .set('notifMsg', notif.removed);
      });
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Email\index.js
  • Dispatch remove action in container
                      
    import { deleteAction } from './reducers/emailActions';
    
    const constDispatchToProps = dispatch => ({
      remove: bindActionCreators(deleteAction, dispatch),
    });
    
    return (
      <EmailList
        emailData={emailData}
        openMail={openMail}
        filterPage={currentPage}
        keyword={keyword}
        moveTo={update}
        remove={remove}
        toggleStar={update}
        reply={this.handleReply}
        loading={loading}
      />
    )
    
                      
                    

Component

  • File: app/components/Email/EmailList.js
  • Call remove function by onClick remove button
                      
    return (
      <IconButton className={classes.button} aria-label="Delete" onClick={() => remove(mail)}><Delete /></IconButton>
    )