Skip to content

Commit

Permalink
feat: email notifications feature (#1012)
Browse files Browse the repository at this point in the history
* feat: add newsletter widget on profile

* feat: add popup to configure newsletter

* style: add darkmode and responsive to newsletter components

* feat: add notification page in settings to manage all newsletters

* feat: added authors list to newsletter settings

* feat: connect newsletter popup to authors in notifications settings page

* fix: rebuild newsletter process with different UI

* feat: add new email address section in newsletter popup

* feat: added list of emails, create subscription, list authors (#1114)

* feat: added list of emails, create subscription, list authors

* feat: ui binding for manage all newsletters

* feat: error handling in email ui

* feat: refetching email subs on create

* style: polish newsletter popup

* fix: refetch newsletter on delete

* style: polish status and delete alert

* fix: newsletter type

* fix: hide newsletter widget when unauth

* style: change newsletter into notification

* fix: add missing popup portal to settings layout

* feat: upload static images used in emails (#1142)

* ci: add cure53 deploy

* fix: add cure53 ipfs addr to bootstrap nodes

* fix: updated socials interface and type (#1169)

* chore: upgrade near-api-js to v1.0.0 (#1155)

* fix: prevent duplicate posts and clearing of editor (#1170)

* chore: remove cure53 bootstrap node

Co-authored-by: Irshad PI <[email protected]>
Co-authored-by: Irshad PI <[email protected]>
Co-authored-by: jack dishman <[email protected]>
Co-authored-by: David Grisham <[email protected]>
Co-authored-by: Rahul Trivedi <[email protected]>
Co-authored-by: Christos Panagiotakopoulos <[email protected]>
  • Loading branch information
7 people authored Sep 28, 2022
1 parent 253eb47 commit 6f0b131
Show file tree
Hide file tree
Showing 14 changed files with 1,111 additions and 13 deletions.
17 changes: 17 additions & 0 deletions .github/configs/cure53.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CONTRACT_NAME=dev-1657702549987-47675900699610
HCAPTCHA_SITE_KEY="7e68122f-db5e-4100-bdab-0240658070d9"

CAPSULE_SERVER=https://cure53.blogchain.app/server
DOMAIN=https://cure53.blogchain.app
STRIPE_PUBLISHABLE_KEY="pk_test_51I81pBCPCJ3FaYLGnUrPUMxipudV7gWWA7qAiqIVMAqnULA4a2uluUgBQxX8yKzAe2iGYOoSMX2rSbF45wtKlhXI00Olk8hJmc"

ORBIT_URL=https://cure53.blogchain.app/orbit
DEBUG=true

# Discord
TORUS_DISCORD_VERIFIER="audit-verifier-august-discord"
TORUS_DISCORD_CLIENTID="906210984396468275"

# Google
TORUS_GOOGLE_VERIFIER="audit-verifier-august"
TORUS_GOOGLE_CLIENTID="653379121360-j8t9ua763vfvd86d1qjguonhrgqvkigo.apps.googleusercontent.com"
20 changes: 7 additions & 13 deletions .github/workflows/ci-release-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ on:
tags:
- alpha
- payments

jobs:

install-dependencies:
runs-on: ubuntu-latest
container:
image: node:16-alpine
steps:

- uses: actions/checkout@v1

- name: install tar (for actions/cache)
Expand Down Expand Up @@ -44,7 +41,6 @@ jobs:
container:
image: node:16-alpine
steps:

- uses: actions/checkout@v1

- uses: actions/download-artifact@v2
Expand All @@ -62,7 +58,6 @@ jobs:
container:
image: node:16-alpine
steps:

- name: install git
run: apk add --no-cache git

Expand Down Expand Up @@ -104,8 +99,8 @@ jobs:
- name: Upload build as release asset
uses: ncipollo/release-action@v1
with:
artifacts: ${{ env.BUILD_NAME }}.tar.gz
token: ${{ secrets.GITREPO_TOKEN }}
artifacts: ${{ env.BUILD_NAME }}.tar.gz
token: ${{ secrets.GITREPO_TOKEN }}
allowUpdates: true

deploy:
Expand All @@ -114,7 +109,6 @@ jobs:
container:
image: lexauw/ansible-alpine:latest
steps:

- name: install git
run: apk add --no-cache git

Expand All @@ -135,9 +129,9 @@ jobs:
- name: Deploy updated images
run: ./.github/scripts/deploy.sh
env:
DEPLOY_ENV: ${{ env.DEPLOY_ENV }}
RELEASE_ID: ${{ env.RELEASE_ID }}
RELEASE_TAG: ${{ env.RELEASE_TAG }}
GITHUB_USER: ${{ secrets.GITREPO_USER }}
GITHUB_TOKEN: ${{ secrets.GITREPO_TOKEN }}
DEPLOY_ENV: ${{ env.DEPLOY_ENV }}
RELEASE_ID: ${{ env.RELEASE_ID }}
RELEASE_TAG: ${{ env.RELEASE_TAG }}
GITHUB_USER: ${{ secrets.GITREPO_USER }}
GITHUB_TOKEN: ${{ secrets.GITREPO_TOKEN }}
ANSIBLE_PRIVATE_KEY: ${{ secrets.ANSIBLE_PRIVATE_KEY }}
119 changes: 119 additions & 0 deletions src/backend/emails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { AxiosError } from 'axios'
import { genericRequest } from './utilities/request'

export enum EmailSubscriptionMode {
AllPosts = `AllPosts`,
Topics = `Topics`,
}

export interface IEmailSubscription {
_id?: string
username?: string
authorID: string
email: string
verified: boolean
createdAt: Date
updatedAt: Date
topics: string[]
mode: EmailSubscriptionMode
}

export interface UserEmail {
email: string
authorSubbed: boolean
}

export async function startEmailSubscription(
authorID: string,
email: string,
topics: string[] = [],
mode: EmailSubscriptionMode = EmailSubscriptionMode.AllPosts,
username?: string,
) {
try {
const body = {
authorID,
email,
topics,
mode,
}
const response = await genericRequest({
method: `post`,
path: `/emails/auth/subscribe`,
username,
body,
})
return response
} catch (err) {
if (err instanceof AxiosError && err.response) {
throw new Error(err.response.data?.error ?? err.message)
}
throw new Error(`Network error: ${err}`)
}
}

export async function listAllAuthors(username: string) {
try {
const response = await genericRequest<{ data: Array<string> }>({
method: `get`,
path: `/emails/authors`,
username,
})
return response.data
} catch (err) {
if (err instanceof AxiosError && err.response) {
throw new Error(err.response.data?.error ?? err.message)
}
throw new Error(`Network error: ${err}`)
}
}

export async function listForAuthor(authorID: string, username: string) {
try {
const response = await genericRequest<{ authorID: string }, { data: Array<IEmailSubscription> }>({
method: `get`,
path: `/emails/subscribed`,
params: { authorID },
username,
})
return response.data
} catch (err) {
if (err instanceof AxiosError && err.response) {
throw new Error(err.response.data?.error ?? err.message)
}
throw new Error(`Network error: ${err}`)
}
}

export async function listEmails(username: string, authorID: string) {
try {
const response = await genericRequest<{ authorID: string }, { data: Array<UserEmail> }>({
method: `get`,
path: `/emails/list`,
username,
params: { authorID },
})
return response.data
} catch (err) {
if (err instanceof AxiosError && err.response) {
throw new Error(err.response.data?.error ?? err.message)
}
throw new Error(`Network error: ${err}`)
}
}

export async function deleteSubscription(username: string, id: string) {
try {
const response = await genericRequest({
method: `delete`,
path: `/emails/${id}`,
username,
})
return response.data
} catch (err) {
if (err instanceof AxiosError && err.response) {
throw new Error(err.response.data?.error ?? err.message)
}
throw new Error(`Network error: ${err}`)
}
}
175 changes: 175 additions & 0 deletions src/components/NewsletterPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<template>
<div class="my-2 flex flex-row items-center justify-between">
<!-- newsletter email -->
<p class="flex items-center text-primary text-sm focus:outline-none mb-2">
{{ newsletter.email }}
</p>
<div class="flex items-center relative">
<div class="">
<span
class="ml-1 flex h-3 w-3 modal-animation mr-4"
@mouseenter="showStatus = true"
@mouseleave="showStatus = false"
>
<span
class="absolute inline-flex h-3 w-3 animate-ping rounded-full opacity-75"
:class="newsletter.verified ? `bg-positive` : `bg-neutral`"
></span>
<span
class="relative inline-flex h-3 w-3 rounded-full"
:class="newsletter.verified ? `bg-positive` : `bg-neutral`"
></span>
</span>
<!-- Info hover -->
<div
v-show="showStatus"
class="absolute w-max z-10 border-lightBorder modal-animation rounded-lg border bg-lightBG dark:bg-gray7 p-2 pr-4 shadow-lg text-gray5 dark:text-gray1 self-center text-xs"
:class="$colorMode.dark ? `EmailInfoOpenDark` : `EmailInfoOpen`"
style="top: -5px; right: 80px"
>
{{ newsletter.verified ? 'Notification is active on this email' : 'Awaiting email verification' }}
</div>
</div>
<!-- delete -->
<div class="icon relative flex items-center">
<button class="focus:outline-none text-gray5 dark:text-gray3 ml-2" @click.stop="toggleDropdownDelete">
<MoreIcon />
</button>
<div
v-show="showDelete"
class="bg-lightBG dark:bg-darkBG dark:text-darkPrimaryText text-lightPrimaryText border-lightBorder modal-animation absolute z-10 flex w-min flex-col rounded-lg border p-2 shadow-lg"
:class="$colorMode.dark ? `deleteEmailOpenDark` : `deleteEmailOpen`"
style="top: 35px; right: -5px"
>
<!-- Delete -->
<button class="focus:outline-none text-negative flex" @click="toggleDeleteConfirm(newsletter._id)">
<BinIcon class="p-1" />
<span class="text-negative ml-1 self-center text-sm pr-1">Delete</span>
</button>
</div>
</div>
</div>
<portal v-if="showDeleteConfirm" to="deleteEmail">
<BasicConfirmAlert
:text="`Are you sure you want to cancel this email notification? You can still add it again later`"
@close="showDeleteConfirm = false"
@confirm="deleteNewsletter(newsletter._id)"
/>
</portal>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import MoreIcon from '@/components/icons/More.vue'
import BinIcon from '@/components/icons/Bin.vue'
import { deleteSubscription } from '@/backend/emails'
import BasicConfirmAlert from '@/components/popups/BasicConfirmAlert.vue'
interface IData {
showDelete: boolean
showDeleteConfirm: boolean
showStatus: boolean
}
export default Vue.extend({
components: {
BinIcon,
MoreIcon,
BasicConfirmAlert,
},
props: {
newsletter: {
type: Object,
required: true,
},
},
data(): IData {
return {
showDelete: false,
showDeleteConfirm: false,
showStatus: false,
}
},
created() {
window.addEventListener(`click`, this.handleDropdown, false)
},
destroyed() {
window.removeEventListener(`click`, this.handleDropdown, false)
},
methods: {
toggleDropdownDelete() {
this.showDelete = !this.showDelete
},
toggleDeleteConfirm() {
this.showDeleteConfirm = !this.showDeleteConfirm
},
async deleteNewsletter(id: string) {
try {
await deleteSubscription(this.$store.state.session.id, id)
this.$toastSuccess(`Newsletter deleted successfully`)
this.$emit(`subscriptionDeleted`)
} catch (err) {
this.$handleError(err)
}
},
handleDropdown(e: any): void {
if (!e.target || e.target.parentNode === null || e.target.parentNode.classList === undefined) {
return
}
if (!e.target.parentNode.classList.contains(`icon`)) {
this.showDelete = false
}
},
},
})
</script>
<style>
.deleteEmailOpen::before {
content: '';
position: absolute;
top: -0.5rem;
right: 0.5rem;
transform: rotate(45deg);
width: 1rem;
height: 1rem;
background-color: #fff;
border-radius: 2px;
}
.deleteEmailOpenDark::before {
content: '';
position: absolute;
top: -0.5rem;
right: 0.5rem;
transform: rotate(45deg);
width: 1rem;
height: 1rem;
background-color: #121212;
border-radius: 2px;
}
.EmailInfoOpen::before {
content: '';
position: absolute;
top: 0.5rem;
right: -0.5rem;
transform: rotate(45deg);
width: 1rem;
height: 1rem;
background-color: #fff;
border-radius: 2px;
z-index: 1;
}
.EmailInfoOpenDark::before {
content: '';
position: absolute;
top: 0.5rem;
right: -0.5rem;
transform: rotate(45deg);
width: 1rem;
height: 1rem;
background-color: #686868;
border-radius: 2px;
z-index: 1;
}
</style>
Loading

0 comments on commit 6f0b131

Please sign in to comment.