admin notifications
This commit is contained in:
parent
426b397b3d
commit
381722de74
|
@ -60,12 +60,14 @@ export default [
|
||||||
{
|
{
|
||||||
id: 'proposal_failed',
|
id: 'proposal_failed',
|
||||||
title: 'Proposal failed',
|
title: 'Proposal failed',
|
||||||
description: 'Sent to the proposal team when the deadline is reached and it didn’t get fully funded',
|
description:
|
||||||
|
'Sent to the proposal team when the deadline is reached and it didn’t get fully funded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'proposal_canceled',
|
id: 'proposal_canceled',
|
||||||
title: 'Proposal canceled',
|
title: 'Proposal canceled',
|
||||||
description: 'Sent to the proposal team when an admin cancels the proposal after funding',
|
description:
|
||||||
|
'Sent to the proposal team when an admin cancels the proposal after funding',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'contribution_confirmed',
|
id: 'contribution_confirmed',
|
||||||
|
@ -85,7 +87,8 @@ export default [
|
||||||
{
|
{
|
||||||
id: 'contribution_proposal_failed',
|
id: 'contribution_proposal_failed',
|
||||||
title: 'Contribution proposal failed',
|
title: 'Contribution proposal failed',
|
||||||
description: 'Sent to contributors when the deadline is reached and the proposal didn’t get fully funded',
|
description:
|
||||||
|
'Sent to contributors when the deadline is reached and the proposal didn’t get fully funded',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'contribution_proposal_canceled',
|
id: 'contribution_proposal_canceled',
|
||||||
|
@ -127,4 +130,19 @@ export default [
|
||||||
title: 'Milestone paid',
|
title: 'Milestone paid',
|
||||||
description: 'Sent when milestone is paid',
|
description: 'Sent when milestone is paid',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'admin_approval',
|
||||||
|
title: 'Admin Approval',
|
||||||
|
description: 'Sent when proposal is ready for review',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin_arbiter',
|
||||||
|
title: 'Admin Arbiter',
|
||||||
|
description: 'Sent when proposal is ready to have an arbiter nominated',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin_payout',
|
||||||
|
title: 'Admin Payout',
|
||||||
|
description: 'Sent when milestone payout has been approved',
|
||||||
|
},
|
||||||
] as Email[];
|
] as Email[];
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
FLASK_APP=app.py
|
FLASK_APP=app.py
|
||||||
FLASK_ENV=development
|
FLASK_ENV=development
|
||||||
SITE_URL="https://zfnd.org" # No trailing slash
|
SITE_URL="https://zfnd.org" # No trailing slash
|
||||||
|
ADMIN_SITE_URL="https://grants-admin.zfnd.org" # No trailing slash
|
||||||
DATABASE_URL="sqlite:////tmp/dev.db"
|
DATABASE_URL="sqlite:////tmp/dev.db"
|
||||||
REDISTOGO_URL="redis://localhost:6379"
|
REDISTOGO_URL="redis://localhost:6379"
|
||||||
SECRET_KEY="not-so-secret"
|
SECRET_KEY="not-so-secret"
|
||||||
|
|
|
@ -165,5 +165,17 @@ example_email_args = {
|
||||||
'amount': '33',
|
'amount': '33',
|
||||||
'tx_explorer_url': 'http://someblockexplorer.com/tx/271857129857192579125',
|
'tx_explorer_url': 'http://someblockexplorer.com/tx/271857129857192579125',
|
||||||
'proposal_milestones_url': 'http://zfnd.org/proposals/999-my-proposal?tab=milestones',
|
'proposal_milestones_url': 'http://zfnd.org/proposals/999-my-proposal?tab=milestones',
|
||||||
}
|
},
|
||||||
|
'admin_approval': {
|
||||||
|
'proposal': proposal,
|
||||||
|
'proposal_url': 'https://grants-admin.zfnd.org/proposals/999',
|
||||||
|
},
|
||||||
|
'admin_arbiter': {
|
||||||
|
'proposal': proposal,
|
||||||
|
'proposal_url': 'https://grants-admin.zfnd.org/proposals/999',
|
||||||
|
},
|
||||||
|
'admin_payout': {
|
||||||
|
'proposal': proposal,
|
||||||
|
'proposal_url': 'https://grants-admin.zfnd.org/proposals/999',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,6 +278,33 @@ def milestone_paid(email_args):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_approval(email_args):
|
||||||
|
return {
|
||||||
|
'subject': f'Review needed for {email_args["proposal"].title}',
|
||||||
|
'title': f'Proposal Review',
|
||||||
|
'preview': f'{email_args["proposal"].title} needs review, as an admin you can help.',
|
||||||
|
'subscription': EmailSubscription.ADMIN_APPROVAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_arbiter(email_args):
|
||||||
|
return {
|
||||||
|
'subject': f'Arbiter needed for {email_args["proposal"].title}',
|
||||||
|
'title': f'Arbiter Nomination',
|
||||||
|
'preview': f'{email_args["proposal"].title} needs an arbiter, as an admin you can help.',
|
||||||
|
'subscription': EmailSubscription.ADMIN_ARBITER,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_payout(email_args):
|
||||||
|
return {
|
||||||
|
'subject': f'Payout requested for {email_args["proposal"].title}',
|
||||||
|
'title': f'Milestone Payout Requested',
|
||||||
|
'preview': f'{email_args["proposal"].title} has requested a payout, as an admin you can help.',
|
||||||
|
'subscription': EmailSubscription.ADMIN_PAYOUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
get_info_lookup = {
|
get_info_lookup = {
|
||||||
'signup': signup_info,
|
'signup': signup_info,
|
||||||
'team_invite': team_invite_info,
|
'team_invite': team_invite_info,
|
||||||
|
@ -303,7 +330,10 @@ get_info_lookup = {
|
||||||
'milestone_request': milestone_request,
|
'milestone_request': milestone_request,
|
||||||
'milestone_reject': milestone_reject,
|
'milestone_reject': milestone_reject,
|
||||||
'milestone_accept': milestone_accept,
|
'milestone_accept': milestone_accept,
|
||||||
'milestone_paid': milestone_paid
|
'milestone_paid': milestone_paid,
|
||||||
|
'admin_approval': admin_approval,
|
||||||
|
'admin_arbiter': admin_arbiter,
|
||||||
|
'admin_payout': admin_payout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,18 @@ class EmailSubscription(Enum):
|
||||||
'bit': 11,
|
'bit': 11,
|
||||||
'key': 'arbiter'
|
'key': 'arbiter'
|
||||||
}
|
}
|
||||||
|
ADMIN_APPROVAL = {
|
||||||
|
'bit': 12,
|
||||||
|
'key': 'admin_approval'
|
||||||
|
}
|
||||||
|
ADMIN_ARBITER = {
|
||||||
|
'bit': 13,
|
||||||
|
'key': 'admin_arbiter'
|
||||||
|
}
|
||||||
|
ADMIN_PAYOUT = {
|
||||||
|
'bit': 14,
|
||||||
|
'key': 'admin_payout'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_email_sub_key(k: str):
|
def is_email_sub_key(k: str):
|
||||||
|
|
|
@ -97,6 +97,7 @@ class Milestone(db.Model):
|
||||||
|
|
||||||
def accept_immediate(self):
|
def accept_immediate(self):
|
||||||
if self.immediate_payout and self.index == 0:
|
if self.immediate_payout and self.index == 0:
|
||||||
|
self.proposal.send_admin_email('admin_payout')
|
||||||
self.date_requested = datetime.datetime.now()
|
self.date_requested = datetime.datetime.now()
|
||||||
self.stage = MilestoneStage.ACCEPTED
|
self.stage = MilestoneStage.ACCEPTED
|
||||||
self.date_accepted = datetime.datetime.now()
|
self.date_accepted = datetime.datetime.now()
|
||||||
|
@ -106,6 +107,7 @@ class Milestone(db.Model):
|
||||||
def accept_request(self, arbiter_id: int):
|
def accept_request(self, arbiter_id: int):
|
||||||
if self.stage != MilestoneStage.REQUESTED:
|
if self.stage != MilestoneStage.REQUESTED:
|
||||||
raise MilestoneException(f'Cannot accept payout request for milestone at {self.stage} stage')
|
raise MilestoneException(f'Cannot accept payout request for milestone at {self.stage} stage')
|
||||||
|
self.proposal.send_admin_email('admin_payout')
|
||||||
self.stage = MilestoneStage.ACCEPTED
|
self.stage = MilestoneStage.ACCEPTED
|
||||||
self.date_accepted = datetime.datetime.now()
|
self.date_accepted = datetime.datetime.now()
|
||||||
self.accept_arbiter_id = arbiter_id
|
self.accept_arbiter_id = arbiter_id
|
||||||
|
|
|
@ -22,7 +22,7 @@ from grant.utils.enums import (
|
||||||
MilestoneStage
|
MilestoneStage
|
||||||
)
|
)
|
||||||
from grant.utils.exceptions import ValidationException
|
from grant.utils.exceptions import ValidationException
|
||||||
from grant.utils.misc import dt_to_unix, make_url, gen_random_id
|
from grant.utils.misc import dt_to_unix, make_url, make_admin_url, gen_random_id
|
||||||
from grant.utils.requests import blockchain_get
|
from grant.utils.requests import blockchain_get
|
||||||
from grant.utils.stubs import anonymous_user
|
from grant.utils.stubs import anonymous_user
|
||||||
|
|
||||||
|
@ -462,6 +462,16 @@ class Proposal(db.Model):
|
||||||
|
|
||||||
return contribution
|
return contribution
|
||||||
|
|
||||||
|
def send_admin_email(self, type: str):
|
||||||
|
from grant.user.models import User
|
||||||
|
admins = User.get_admins()
|
||||||
|
for a in admins:
|
||||||
|
send_email(a.email_address, type, {
|
||||||
|
'user': a,
|
||||||
|
'proposal': self,
|
||||||
|
'proposal_url': make_admin_url(f'/proposals/{self.id}'),
|
||||||
|
})
|
||||||
|
|
||||||
# state: status (DRAFT || REJECTED) -> (PENDING || STAKING)
|
# state: status (DRAFT || REJECTED) -> (PENDING || STAKING)
|
||||||
def submit_for_approval(self):
|
def submit_for_approval(self):
|
||||||
self.validate_publishable()
|
self.validate_publishable()
|
||||||
|
@ -485,6 +495,7 @@ class Proposal(db.Model):
|
||||||
raise ValidationException(f"Proposal status must be staking in order to be set to pending")
|
raise ValidationException(f"Proposal status must be staking in order to be set to pending")
|
||||||
if not self.is_staked:
|
if not self.is_staked:
|
||||||
raise ValidationException(f"Proposal is not fully staked, cannot set to pending")
|
raise ValidationException(f"Proposal is not fully staked, cannot set to pending")
|
||||||
|
self.send_admin_email('admin_approval')
|
||||||
self.status = ProposalStatus.PENDING
|
self.status = ProposalStatus.PENDING
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
@ -543,6 +554,7 @@ class Proposal(db.Model):
|
||||||
raise ValidationException(f"Proposal stage must be funding_required in order transition to funded state")
|
raise ValidationException(f"Proposal stage must be funding_required in order transition to funded state")
|
||||||
if not self.is_funded:
|
if not self.is_funded:
|
||||||
raise ValidationException(f"Proposal is not fully funded, cannot set to funded state")
|
raise ValidationException(f"Proposal is not fully funded, cannot set to funded state")
|
||||||
|
self.send_admin_email('admin_arbiter')
|
||||||
self.stage = ProposalStage.WIP
|
self.stage = ProposalStage.WIP
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
|
@ -15,6 +15,7 @@ env.read_env()
|
||||||
ENV = env.str("FLASK_ENV", default="production")
|
ENV = env.str("FLASK_ENV", default="production")
|
||||||
DEBUG = ENV == "development"
|
DEBUG = ENV == "development"
|
||||||
SITE_URL = env.str('SITE_URL', default='https://zfnd.org')
|
SITE_URL = env.str('SITE_URL', default='https://zfnd.org')
|
||||||
|
ADMIN_SITE_URL = env.str('ADMIN_SITE_URL', default='https://grants-admin.zfnd.org')
|
||||||
E2E_TESTING = env.str("E2E_TESTING", default=None)
|
E2E_TESTING = env.str("E2E_TESTING", default=None)
|
||||||
E2E_DATABASE_URL = env.str("E2E_DATABASE_URL", default=None)
|
E2E_DATABASE_URL = env.str("E2E_DATABASE_URL", default=None)
|
||||||
SQLALCHEMY_DATABASE_URI = E2E_DATABASE_URL if E2E_TESTING else env.str("DATABASE_URL")
|
SQLALCHEMY_DATABASE_URI = E2E_DATABASE_URL if E2E_TESTING else env.str("DATABASE_URL")
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<p style="margin: 0 0 20px;">
|
||||||
|
<a href="{{ args.proposal_url }}" target="_blank">
|
||||||
|
{{ args.proposal.title }}</a
|
||||||
|
>
|
||||||
|
is awaiting approval. As an admin you can help out by reviewing it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
align="center"
|
||||||
|
style="border-radius: 3px;"
|
||||||
|
bgcolor="{{ UI.PRIMARY }}"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{ args.proposal_url }}"
|
||||||
|
target="_blank"
|
||||||
|
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid {{
|
||||||
|
UI.PRIMARY
|
||||||
|
}}; display: inline-block;"
|
||||||
|
>
|
||||||
|
Review Proposal
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{ args.proposal.title }} is awaiting approval. As an admin you can help out by reviewing it.
|
||||||
|
|
||||||
|
Visit the proposal and review: {{ args.proposal_url }}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<p style="margin: 0 0 20px;">
|
||||||
|
<a href="{{ args.proposal_url }}" target="_blank">
|
||||||
|
{{ args.proposal.title }}</a
|
||||||
|
>
|
||||||
|
needs an arbiter. As an admin you can help out by nominating one.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
align="center"
|
||||||
|
style="border-radius: 3px;"
|
||||||
|
bgcolor="{{ UI.PRIMARY }}"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{ args.proposal_url }}"
|
||||||
|
target="_blank"
|
||||||
|
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid {{
|
||||||
|
UI.PRIMARY
|
||||||
|
}}; display: inline-block;"
|
||||||
|
>
|
||||||
|
Nominate Arbiter
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{ args.proposal.title }} needs an arbiter. As an admin you can help out by nominating one.
|
||||||
|
|
||||||
|
Visit the proposal and nominate an arbiter: {{ args.proposal_url }}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<p style="margin: 0 0 20px;">
|
||||||
|
<a href="{{ args.proposal_url }}" target="_blank">
|
||||||
|
{{ args.proposal.title }}</a
|
||||||
|
>
|
||||||
|
has an approved payout. As an admin you can help out by paying the milestone
|
||||||
|
and marking it paid.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
align="center"
|
||||||
|
style="border-radius: 3px;"
|
||||||
|
bgcolor="{{ UI.PRIMARY }}"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="{{ args.proposal_url }}"
|
||||||
|
target="_blank"
|
||||||
|
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid {{
|
||||||
|
UI.PRIMARY
|
||||||
|
}}; display: inline-block;"
|
||||||
|
>
|
||||||
|
Pay Milestone
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
|
@ -0,0 +1,4 @@
|
||||||
|
{{ args.proposal.title }} has an approved payout. As an admin you can help out by
|
||||||
|
paying the milestone and marking it paid.
|
||||||
|
|
||||||
|
Pay the milestone: {{ args.proposal_url }}
|
|
@ -198,6 +198,10 @@ class User(db.Model, UserMixin):
|
||||||
def get_by_email(email_address: str):
|
def get_by_email(email_address: str):
|
||||||
return security.datastore.get_user(email_address)
|
return security.datastore.get_user(email_address)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_admins():
|
||||||
|
return User.query.filter(User.is_admin == True).all()
|
||||||
|
|
||||||
def check_password(self, password: str):
|
def check_password(self, password: str):
|
||||||
return verify_and_update_password(password, self)
|
return verify_and_update_password(password, self)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import re
|
||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from grant.settings import SITE_URL, EXPLORER_URL
|
from grant.settings import SITE_URL, ADMIN_SITE_URL, EXPLORER_URL
|
||||||
|
|
||||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||||
RANDOM_CHARS = string.ascii_letters + string.digits
|
RANDOM_CHARS = string.ascii_letters + string.digits
|
||||||
|
@ -37,6 +37,10 @@ def make_url(path: str):
|
||||||
return f'{SITE_URL}{path}'
|
return f'{SITE_URL}{path}'
|
||||||
|
|
||||||
|
|
||||||
|
def make_admin_url(path: str):
|
||||||
|
return f'{ADMIN_SITE_URL}{path}'
|
||||||
|
|
||||||
|
|
||||||
def make_explore_url(txid: str):
|
def make_explore_url(txid: str):
|
||||||
return EXPLORER_URL.replace('<txid>', txid)
|
return EXPLORER_URL.replace('<txid>', txid)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ interface OwnProps {
|
||||||
emailSubscriptions: EmailSubscriptions;
|
emailSubscriptions: EmailSubscriptions;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onSubmit: (settings: EmailSubscriptions) => void;
|
onSubmit: (settings: EmailSubscriptions) => void;
|
||||||
|
isAdmin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OwnProps & FormComponentProps;
|
type Props = OwnProps & FormComponentProps;
|
||||||
|
@ -18,9 +19,9 @@ class EmailSubscriptionsForm extends React.Component<Props, {}> {
|
||||||
this.props.form.setFieldsValue(this.props.emailSubscriptions);
|
this.props.form.setFieldsValue(this.props.emailSubscriptions);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { emailSubscriptions, loading } = this.props;
|
const { emailSubscriptions, loading, isAdmin } = this.props;
|
||||||
const { getFieldDecorator } = this.props.form;
|
const { getFieldDecorator } = this.props.form;
|
||||||
const groupedSubs = groupEmailSubscriptionsByCategory(emailSubscriptions);
|
const groupedSubs = groupEmailSubscriptionsByCategory(emailSubscriptions, isAdmin);
|
||||||
|
|
||||||
const fields = Object.entries(this.props.form.getFieldsValue());
|
const fields = Object.entries(this.props.form.getFieldsValue());
|
||||||
const numChecked = fields.map(([_, v]) => v).filter(v => v).length;
|
const numChecked = fields.map(([_, v]) => v).filter(v => v).length;
|
||||||
|
|
|
@ -45,6 +45,7 @@ class EmailSubscriptions extends React.Component<Props, State> {
|
||||||
emailSubscriptions={emailSubscriptions}
|
emailSubscriptions={emailSubscriptions}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onSubmit={this.setSubscriptions}
|
onSubmit={this.setSubscriptions}
|
||||||
|
isAdmin={!!authUser.isAdmin}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -62,6 +63,15 @@ class EmailSubscriptions extends React.Component<Props, State> {
|
||||||
private setSubscriptions = (emailSubscriptions: IEmailSubscriptions) => {
|
private setSubscriptions = (emailSubscriptions: IEmailSubscriptions) => {
|
||||||
const { authUser } = this.props;
|
const { authUser } = this.props;
|
||||||
if (!authUser) return;
|
if (!authUser) return;
|
||||||
|
if (!authUser.isAdmin) {
|
||||||
|
// fill in api-required fields which are not populated for non-admin form
|
||||||
|
emailSubscriptions = {
|
||||||
|
adminApproval: true,
|
||||||
|
adminArbiter: true,
|
||||||
|
adminPayout: true,
|
||||||
|
...emailSubscriptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
updateUserSettings(authUser.userid, { emailSubscriptions })
|
updateUserSettings(authUser.userid, { emailSubscriptions })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|
|
@ -72,6 +72,23 @@ export const EMAIL_SUBSCRIPTIONS: { [key in ESKey]: EmailSubscriptionInfo } = {
|
||||||
category: EMAIL_SUBSCRIPTION_CATEGORY.PROPOSAL,
|
category: EMAIL_SUBSCRIPTION_CATEGORY.PROPOSAL,
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ADMIN
|
||||||
|
adminApproval: {
|
||||||
|
description: 'proposal needs review',
|
||||||
|
category: EMAIL_SUBSCRIPTION_CATEGORY.ADMIN,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
adminArbiter: {
|
||||||
|
description: 'proposal needs arbiter',
|
||||||
|
category: EMAIL_SUBSCRIPTION_CATEGORY.ADMIN,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
adminPayout: {
|
||||||
|
description: 'milestone needs payout',
|
||||||
|
category: EMAIL_SUBSCRIPTION_CATEGORY.ADMIN,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EMAIL_SUBSCRIPTION_CATEGORIES: {
|
export const EMAIL_SUBSCRIPTION_CATEGORIES: {
|
||||||
|
@ -82,10 +99,18 @@ export const EMAIL_SUBSCRIPTION_CATEGORIES: {
|
||||||
[EMAIL_SUBSCRIPTION_CATEGORY.FUNDED]: {
|
[EMAIL_SUBSCRIPTION_CATEGORY.FUNDED]: {
|
||||||
description: 'Proposals you have contributed to',
|
description: 'Proposals you have contributed to',
|
||||||
},
|
},
|
||||||
|
[EMAIL_SUBSCRIPTION_CATEGORY.ADMIN]: { description: 'Admin' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const groupEmailSubscriptionsByCategory = (es: EmailSubscriptions) => {
|
export const groupEmailSubscriptionsByCategory = (
|
||||||
return Object.entries(EMAIL_SUBSCRIPTION_CATEGORIES).map(([k, v]) => {
|
es: EmailSubscriptions,
|
||||||
|
withAdmin: boolean,
|
||||||
|
) => {
|
||||||
|
const catsForUser = { ...EMAIL_SUBSCRIPTION_CATEGORIES };
|
||||||
|
if (!withAdmin) {
|
||||||
|
delete catsForUser.ADMIN;
|
||||||
|
}
|
||||||
|
return Object.entries(catsForUser).map(([k, v]) => {
|
||||||
const subscriptionSettings = Object.entries(EMAIL_SUBSCRIPTIONS)
|
const subscriptionSettings = Object.entries(EMAIL_SUBSCRIPTIONS)
|
||||||
.filter(([_, sv]) => sv.category === k)
|
.filter(([_, sv]) => sv.category === k)
|
||||||
.map(([sk, sv]) => {
|
.map(([sk, sv]) => {
|
||||||
|
|
|
@ -12,12 +12,16 @@ export interface EmailSubscriptions {
|
||||||
myProposalFunded: boolean;
|
myProposalFunded: boolean;
|
||||||
myProposalRefund: boolean;
|
myProposalRefund: boolean;
|
||||||
arbiter: boolean;
|
arbiter: boolean;
|
||||||
|
adminApproval: boolean;
|
||||||
|
adminArbiter: boolean;
|
||||||
|
adminPayout: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EMAIL_SUBSCRIPTION_CATEGORY {
|
export enum EMAIL_SUBSCRIPTION_CATEGORY {
|
||||||
GENERAL = 'GENERAL',
|
GENERAL = 'GENERAL',
|
||||||
PROPOSAL = 'PROPOSAL',
|
PROPOSAL = 'PROPOSAL',
|
||||||
FUNDED = 'FUNDED',
|
FUNDED = 'FUNDED',
|
||||||
|
ADMIN = 'ADMIN',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmailSubscriptionInfo {
|
export interface EmailSubscriptionInfo {
|
||||||
|
|
|
@ -9,6 +9,7 @@ export interface User {
|
||||||
title: string;
|
title: string;
|
||||||
socialMedias: SocialMedia[];
|
socialMedias: SocialMedia[];
|
||||||
avatar: { imageUrl: string } | null;
|
avatar: { imageUrl: string } | null;
|
||||||
|
isAdmin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
|
|
Loading…
Reference in New Issue