From 6902292696a71f8ec62250aa7f62ecd3f80fe2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 6 Sep 2023 21:06:15 -0300 Subject: [PATCH 1/2] Create content rule to send an email to the Foundation Member on renewal cycle --- .../src/ploneorg/src/ploneorg/configure.zcml | 1 + .../src/ploneorg/interpolators/__init__.py | 0 .../src/ploneorg/interpolators/configure.zcml | 20 ++++++ .../src/ploneorg/interpolators/member.py | 26 ++++++++ .../profiles/default/contentrules.xml | 62 +++++++++++++++++++ .../ploneorg/profiles/default/metadata.xml | 2 +- .../src/ploneorg/upgrades/configure.zcml | 10 +++ backend/src/ploneorg/tests/test_setup.py | 2 +- backend/src/ploneorg/tests/test_upgrades.py | 1 + 9 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 backend/src/ploneorg/src/ploneorg/interpolators/__init__.py create mode 100644 backend/src/ploneorg/src/ploneorg/interpolators/configure.zcml create mode 100644 backend/src/ploneorg/src/ploneorg/interpolators/member.py create mode 100644 backend/src/ploneorg/src/ploneorg/profiles/default/contentrules.xml diff --git a/backend/src/ploneorg/src/ploneorg/configure.zcml b/backend/src/ploneorg/src/ploneorg/configure.zcml index ee223aa..e78f4c2 100644 --- a/backend/src/ploneorg/src/ploneorg/configure.zcml +++ b/backend/src/ploneorg/src/ploneorg/configure.zcml @@ -27,5 +27,6 @@ + diff --git a/backend/src/ploneorg/src/ploneorg/interpolators/__init__.py b/backend/src/ploneorg/src/ploneorg/interpolators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/ploneorg/src/ploneorg/interpolators/configure.zcml b/backend/src/ploneorg/src/ploneorg/interpolators/configure.zcml new file mode 100644 index 0000000..a3494f6 --- /dev/null +++ b/backend/src/ploneorg/src/ploneorg/interpolators/configure.zcml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/backend/src/ploneorg/src/ploneorg/interpolators/member.py b/backend/src/ploneorg/src/ploneorg/interpolators/member.py new file mode 100644 index 0000000..ef149de --- /dev/null +++ b/backend/src/ploneorg/src/ploneorg/interpolators/member.py @@ -0,0 +1,26 @@ +from plone.stringinterp.adapters import BaseSubstitution +from ploneorg import _ +from ploneorg.content.member import IFoundationMember +from zope.component import adapter + + +@adapter(IFoundationMember) +class EmailSubstitution(BaseSubstitution): + category = _("Foundation Member") + description = _("Member E-mail") + + def safe_call(self): + name = self.context.title + email = self.context.email + if name and email: + return f"{name}<{email}>" + + +@adapter(IFoundationMember) +class RenewURLSubstitution(BaseSubstitution): + category = _("Foundation Member") + description = _("Renew URL") + + def safe_call(self): + url = self.context.absolute_url() + return f"{url}/renew_membership>" diff --git a/backend/src/ploneorg/src/ploneorg/profiles/default/contentrules.xml b/backend/src/ploneorg/src/ploneorg/profiles/default/contentrules.xml new file mode 100644 index 0000000..1370412 --- /dev/null +++ b/backend/src/ploneorg/src/ploneorg/profiles/default/contentrules.xml @@ -0,0 +1,62 @@ + + + + + + + FoundationMember + + + + + suspend + + + + + + Reminder: Time to Renew Your Plone Foundation Membership + Plone Foundation<ploneorg@mg.plone.org> + ${pf_member_email} + True + Dear ${title}, + +As we approach another exciting year for the Plone Foundation, it's time to renew your membership to maintain your active status and continue enjoying the benefits that come with it. As an active member, you have the privilege to: + + 1. Vote for the Plone Foundation Board of Directors + 2. Participate in selecting the location for the next Plone Conference + 3. Approve the minutes of the annual meeting + 4. Exercise all other rights and privileges granted to Plone Foundation members + +How to Renew Your Membership + + 1. Log in to your account at https://plone.org/login using your GitHub credentials. + 2. Navigate to ${pf_renew_url} and click on the Renew Membership button. + +If you no longer wish to maintain an active membership, you may choose to retire it by clicking on the Retire Membership button. + +Need Assistance? + +Should you encounter any issues or have questions regarding the renewal process, please don't hesitate to reach out to us at board@plone.org. We are here to assist you. + +We look forward to another year of collaboration and growth with you as part of the Plone Foundation. + +Best regards, +Plone Foundation Board +board@plone.org + + + + + diff --git a/backend/src/ploneorg/src/ploneorg/profiles/default/metadata.xml b/backend/src/ploneorg/src/ploneorg/profiles/default/metadata.xml index fc7455b..8ce443b 100644 --- a/backend/src/ploneorg/src/ploneorg/profiles/default/metadata.xml +++ b/backend/src/ploneorg/src/ploneorg/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 20230904001 + 20230907001 profile-plone.volto:default profile-plone.app.vulnerabilities:default diff --git a/backend/src/ploneorg/src/ploneorg/upgrades/configure.zcml b/backend/src/ploneorg/src/ploneorg/upgrades/configure.zcml index 5ab33ae..783381d 100644 --- a/backend/src/ploneorg/src/ploneorg/upgrades/configure.zcml +++ b/backend/src/ploneorg/src/ploneorg/upgrades/configure.zcml @@ -153,4 +153,14 @@ /> + + + diff --git a/backend/src/ploneorg/tests/test_setup.py b/backend/src/ploneorg/tests/test_setup.py index a35c942..e11952e 100644 --- a/backend/src/ploneorg/tests/test_setup.py +++ b/backend/src/ploneorg/tests/test_setup.py @@ -34,7 +34,7 @@ def test_latest_version(self): """Test latest version of default profile.""" self.assertEqual( self.setup.getLastVersionForProfile("ploneorg:default")[0], - "20230904001", + "20230907001", ) diff --git a/backend/src/ploneorg/tests/test_upgrades.py b/backend/src/ploneorg/tests/test_upgrades.py index b321a39..e862197 100644 --- a/backend/src/ploneorg/tests/test_upgrades.py +++ b/backend/src/ploneorg/tests/test_upgrades.py @@ -40,6 +40,7 @@ def available_steps(self, src, dst) -> list: ("20230412001", "20230528001", 1), ("20230528001", "20230530001", 1), ("20230530001", "20230904001", 1), + ("20230904001", "20230907001", 1), ] ) From 0e519dc9cdec6c2c2abd6421cf8e6e303a53812c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Wed, 6 Sep 2023 21:06:36 -0300 Subject: [PATCH 2/2] Prepare renew cycle --- backend/Makefile | 4 +- backend/src/ploneorg/setup.py | 1 + .../src/ploneorg/interpolators/member.py | 2 +- .../src/components/Views/FoundationMember.jsx | 213 +++++++++++------- frontend/src/components/Views/member.less | 31 +++ frontend/src/routes.js | 6 +- 6 files changed, 173 insertions(+), 84 deletions(-) create mode 100644 frontend/src/components/Views/member.less diff --git a/backend/Makefile b/backend/Makefile index 67d33c7..82ff654 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -157,7 +157,7 @@ create-site: instance/etc/zope.ini ## Create a new site from scratch .PHONY: start start: ## Start a Plone instance on localhost:8080 - PYTHONWARNINGS=ignore ./bin/runwsgi instance/etc/zope.ini + ENABLE_PRINTING_MAILHOST=True PYTHONWARNINGS=ignore ./bin/runwsgi instance/etc/zope.ini .PHONY: debug debug: instance/etc/zope.ini ## Run debug console @@ -178,4 +178,4 @@ create-tag: # Create a new tag using git commit-and-release: # Commit new version change and create tag @echo "Commiting changes" @git commit -am "Tag release as $(PROJECT_VERSION) to deploy" - make create-tag \ No newline at end of file + make create-tag diff --git a/backend/src/ploneorg/setup.py b/backend/src/ploneorg/setup.py index 8598990..4d38977 100644 --- a/backend/src/ploneorg/setup.py +++ b/backend/src/ploneorg/setup.py @@ -66,6 +66,7 @@ "plone.app.testing[robot]>=7.0.0a3", "plone.restapi[test]", "collective.MockMailHost", + "Products.PrintingMailHost", "pytest", ], }, diff --git a/backend/src/ploneorg/src/ploneorg/interpolators/member.py b/backend/src/ploneorg/src/ploneorg/interpolators/member.py index ef149de..47f3071 100644 --- a/backend/src/ploneorg/src/ploneorg/interpolators/member.py +++ b/backend/src/ploneorg/src/ploneorg/interpolators/member.py @@ -23,4 +23,4 @@ class RenewURLSubstitution(BaseSubstitution): def safe_call(self): url = self.context.absolute_url() - return f"{url}/renew_membership>" + return f"{url}?mtm_campaign=PFM&mtm_kwd=Renew" diff --git a/frontend/src/components/Views/FoundationMember.jsx b/frontend/src/components/Views/FoundationMember.jsx index 79ee62a..ca8613e 100644 --- a/frontend/src/components/Views/FoundationMember.jsx +++ b/frontend/src/components/Views/FoundationMember.jsx @@ -3,10 +3,14 @@ * @module components/View/FoundationMemberView */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { Container, Table } from 'semantic-ui-react'; -import { flattenHTMLToAppURL } from '@plone/volto/helpers'; +import { Container, Table, Button } from 'semantic-ui-react'; +import { flattenHTMLToAppURL, flattenToAppURL } from '@plone/volto/helpers'; +import { useSelector, useDispatch } from 'react-redux'; +import { getWorkflow, transitionWorkflow } from '@plone/volto/actions'; + +import './member.less'; /** * FoundationMemberView view component class. @@ -14,85 +18,142 @@ import { flattenHTMLToAppURL } from '@plone/volto/helpers'; * @params {object} content Content object. * @returns {string} Markup of the component. */ -const FoundationMemberView = ({ content }) => ( - - {content.title &&

{content.title}

} -

- {content.organization && ( +const FoundationMemberView = (props) => { + const pathname = props.location.pathname; + const { content } = props; + const dispatch = useDispatch(); + const transitionLoading = useSelector( + (state) => state.workflow?.transition?.loading || false, + ); + const transitions = useSelector((state) => state.workflow?.transitions); + const currentState = useSelector((state) => state.workflow?.currentState); + const [transition, setTransition] = useState(''); + const allowedTransitions = ['Renew']; + + useEffect(() => { + if (transitionLoading === false) { + dispatch(getWorkflow(pathname)); + } + }, [dispatch, pathname, transitionLoading]); + + useEffect(() => { + if (transition) { + dispatch(transitionWorkflow(transition)); + setTransition(''); + } + }, [dispatch, transition]); + + const handleTransition = (action) => { + setTransition(flattenToAppURL(action)); + }; + + const displayActions = currentState && transitions && transitions.length > 0; + + return ( + + {content.title && ( +

{content.title}

+ )} +

+ {content.organization && ( + <> + {content.organization}{' '} + + )} + {content.city && ( + <> + {content.city} ( + {content.country.title}) + + )} +

+ + {displayActions && ( + <> +

Actions

+ + + {transitions.map(function (transition, i) { + return ( + allowedTransitions.includes(transition.title) && ( + + ) + ); + })} + + + + )} + {content.email && ( <> - {content.organization}{' '} +

Contact Information

+ + + + + E-mail + + {content.email ? ( + {content.email} + ) : ( + '' + )} + + + + Address + + {content.address ? content.address : '-'} + + + + City + + {content.city ? content.city : '-'} + + + + State + + {content.state ? content.state : '-'} + + + + Postal Code + + {content.postal_code ? content.postal_code : '-'} + + + + Country + + {content.country ? content.country.title : '-'} + + + +
+
)} - {content.city && ( + {content.merit && ( <> - {content.city} ( - {content.country.title}) +

Contributions

+
)} -

- {content.email && ( - <> -

Contact Information

- - - - - E-mail - - {content.email ? ( - {content.email} - ) : ( - '' - )} - - - - Address - - {content.address ? content.address : '-'} - - - - City - - {content.city ? content.city : '-'} - - - - State - - {content.state ? content.state : '-'} - - - - Postal Code - - {content.postal_code ? content.postal_code : '-'} - - - - Country - - {content.country ? content.country.title : '-'} - - - -
-
- - )} - {content.merit && ( - <> -

Contributions

-
- - )} - -); + + ); +}; /** * Property types. diff --git a/frontend/src/components/Views/member.less b/frontend/src/components/Views/member.less new file mode 100644 index 0000000..f507a72 --- /dev/null +++ b/frontend/src/components/Views/member.less @@ -0,0 +1,31 @@ +@blue: #007db9; +@yellow: #efbd0b; +@green: #5eb618; +@lightGrey: #cdcdcd; + +div.ui.container.memberActions { + display: flex; + align-items: center; + justify-content: center; + + > .actions { + text-align: center; + } +} + +button.ui.button.transition { + padding: 2rem 4rem; + font-size: 14px; + font-weight: 700; + + &.transition-Renew { + background-color: @green; + color: white; + } + + &.transition-Retire { + margin-left: 3rem; + background-color: @lightGrey; + color: black; + } +} diff --git a/frontend/src/routes.js b/frontend/src/routes.js index ff5d54f..cdf074d 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -16,11 +16,7 @@ const routes = [ { path: '/', component: App, // Change this if you want a different component - routes: [ - // Add your routes here - ...(config.addonRoutes || []), - ...defaultRoutes, - ], + routes: [...(config.addonRoutes || []), ...defaultRoutes], }, ];