diff --git a/app/controllers/admin/newsletter.js b/app/controllers/admin/newsletter.js index f84d0bf..1b58ae7 100644 --- a/app/controllers/admin/newsletter.js +++ b/app/controllers/admin/newsletter.js @@ -7,7 +7,7 @@ const DTP_COMPONENT_NAME = 'admin:newsletter'; const express = require('express'); -const { SiteController } = require('../../../lib/site-lib'); +const { SiteController, SiteError } = require('../../../lib/site-lib'); class NewsletterController extends SiteController { @@ -33,6 +33,8 @@ class NewsletterController extends SiteController { router.get('/', this.getIndex.bind(this)); + router.delete('/:newsletterId', this.deleteNewsletter.bind(this)); + return router; } @@ -40,6 +42,9 @@ class NewsletterController extends SiteController { const { newsletter: newsletterService } = this.dtp.services; try { res.locals.newsletter = await newsletterService.getById(newsletterId); + if (!res.locals.newsletter) { + throw new SiteError(404, 'Newsletter not found'); + } return next(); } catch (error) { this.log.error('failed to populate newsletterId', { newsletterId, error }); @@ -62,7 +67,7 @@ class NewsletterController extends SiteController { const { newsletter: newsletterService } = this.dtp.services; try { const newsletter = await newsletterService.create(req.user, req.body); - res.redirect(`/admin/newsletter/${newsletter._id}`); + res.redirect('/admin/newsletter'); } catch (error) { this.log.error('failed to update newsletter', { error }); return next(error); @@ -83,6 +88,33 @@ class NewsletterController extends SiteController { return next(error); } } + + async deleteNewsletter (req, res, next) { + const { newsletter: newsletterService, displayEngine: displayEngineService } = this.dtp.services; + try { + const displayList = displayEngineService.createDisplayList('delete-newsletter'); + + await newsletterService.deleteNewsletter(res.locals.newsletter); + + displayList.removeElement(`li[data-newsletter-id="${res.locals.newsletter._id}"]`); + displayList.showNotification( + `Newsletter "${res.locals.newsletter.title}" deleted`, + 'success', + 'bottom-center', + 3000, + ); + res.status(200).json({ success: true, displayList }); + } catch (error) { + this.log.error('failed to delete newsletter', { + newsletterId: res.local.newsletter._id, + error, + }); + res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } } module.exports = async (dtp) => { diff --git a/app/services/newsletter.js b/app/services/newsletter.js index cd66f97..bcc4c89 100644 --- a/app/services/newsletter.js +++ b/app/services/newsletter.js @@ -109,6 +109,11 @@ class NewsletterService extends SiteService { return recipient.toObject(); } + + async deleteNewsletter (newsletter) { + this.log.info('deleting newsletter', { newsletterId: newsletter._id }); + await Newsletter.deleteOne({ _id: newsletter._id }); + } } module.exports = { diff --git a/app/views/admin/newsletter/index.pug b/app/views/admin/newsletter/index.pug index 4d9071e..b0dec46 100644 --- a/app/views/admin/newsletter/index.pug +++ b/app/views/admin/newsletter/index.pug @@ -7,26 +7,33 @@ block content h1.uk-text-truncate Newsletters .uk-width-auto a(href="/admin/newsletter/compose").uk-button.dtp-button-primary - span - i.fas.fa-plus - span(class="uk-visible@m").uk-margin-small-left New Newsletter + +renderButtonIcon('fa-plus', 'New Newsletter') .uk-margin if (Array.isArray(newsletters) && (newsletters.length > 0)) + ul.uk-list + each newsletter in newsletters - li + + li(data-newsletter-id= newsletter._id) div(uk-grid).uk-grid-small.uk-flex-middle .uk-width-expand a(href=`/admin/newsletter/${newsletter._id}`).uk-display-block.uk-text-large.uk-text-truncate= newsletter.title + .uk-width-auto - button(type="button").uk-button.dtp-button-default - span - i.fas.fa-trash - span(class="uk-visible@m").uk-margin-small-left Delete - button(type="button").uk-button.dtp-button-default - span - i.fas.fa-paper-plane - span(class="uk-visible@m").uk-margin-small-left Send + div(uk-grid).uk-grid-small + .uk-width-auto + button( + type="button", + data-newsletter-id= newsletter._id, + data-newsletter-title= newsletter.title, + onclick="return dtp.adminApp.deleteNewsletter(event);", + ).uk-button.dtp-button-danger + +renderButtonIcon('fa-trash', 'Delete') + + .uk-width-auto + button(type="button").uk-button.dtp-button-default + +renderButtonIcon('fa-paper-plane', 'Send') else div There are no newsletters at this time. \ No newline at end of file diff --git a/app/views/components/button-icon.pug b/app/views/components/button-icon.pug new file mode 100644 index 0000000..1bb88bf --- /dev/null +++ b/app/views/components/button-icon.pug @@ -0,0 +1,4 @@ +mixin renderButtonIcon (buttonClass, buttonLabel) + span + i(class=`fas ${buttonClass}`) + span(class="uk-visible@m").uk-margin-small-left= buttonLabel \ No newline at end of file diff --git a/app/views/components/library.pug b/app/views/components/library.pug index 0bbf826..3b83fa6 100644 --- a/app/views/components/library.pug +++ b/app/views/components/library.pug @@ -1,5 +1,7 @@ //- common routines for all views everywhere +include button-icon + - function formatCount(value) { value = value || 0; diff --git a/client/js/site-admin-host-stats-app.js b/client/js/site-admin-host-stats-app.js index 2a2e9d8..9d6fe7a 100644 --- a/client/js/site-admin-host-stats-app.js +++ b/client/js/site-admin-host-stats-app.js @@ -22,6 +22,7 @@ const CHART_LINE_RX_SEC = 'rgb(6, 154, 240)'; import DtpApp from 'dtp/dtp-app.js'; import numeral from 'numeral'; +import UIkit from 'uikit'; // import UIkit from 'uikit'; export default class DtpSiteAdminHostStatsApp extends DtpApp { @@ -200,6 +201,30 @@ export default class DtpSiteAdminHostStatsApp extends DtpApp { this.charts.interfaces.push(chart); }); } + + async deleteNewsletter (event) { + const newsletterId = event.currentTarget.getAttribute('data-newsletter-id'); + const newsletterTitle = event.currentTarget.getAttribute('data-newsletter-title'); + console.log(newsletterId, newsletterTitle); + try { + await UIkit.modal.confirm(`Are you sure you want to delete "${newsletterTitle}"`); + } catch (error) { + this.log.info('deleteNewsletter', 'aborted'); + return; + } + try { + const response = await fetch(`/admin/newsletter/${newsletterId}`, { + method: 'DELETE', + }); + if (!response.ok) { + throw new Error('Failed to delete newsletter'); + } + await this.processResponse(response); + } catch (error) { + this.log.error('deleteNewsletter', 'failed to delete newsletter', { newsletterId, newsletterTitle, error }); + UIkit.modal.alert(`Failed to delete newsletter: ${error.message}`); + } + } } dtp.DtpSiteAdminHostStatsApp = DtpSiteAdminHostStatsApp; \ No newline at end of file