diff --git a/package.json b/package.json
index 330e49d..6c883fd 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,63 @@
"pre-commit": "lint-staged"
}
},
+ "stylelint": {
+ "extends": [
+ "stylelint-config-idiomatic-order"
+ ],
+ "plugins": [
+ "stylelint-prettier"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "**/*.scss"
+ ],
+ "customSyntax": "postcss-scss"
+ },
+ {
+ "files": [
+ "**/*.less"
+ ],
+ "customSyntax": "postcss-less"
+ },
+ {
+ "files": [
+ "**/*.overrides"
+ ],
+ "customSyntax": "postcss-less"
+ }
+ ],
+ "rules": {
+ "prettier/prettier": true,
+ "rule-empty-line-before": [
+ "always-multi-line",
+ {
+ "except": [
+ "first-nested"
+ ],
+ "ignore": [
+ "after-comment"
+ ]
+ }
+ ]
+ }
+ },
+ "lint-staged": {
+ "src/**/*.{js,jsx,ts,tsx,json}": [
+ "npx eslint --max-warnings=0 --fix",
+ "npx prettier --single-quote --write"
+ ],
+ "src/**/*.{css,less}": [
+ "npx stylelint --fix"
+ ],
+ "src/**/*.scss": [
+ "npx stylelint --fix --customSyntax postcss-scss"
+ ],
+ "src/**/*.overrides": [
+ "npx stylelint --fix --syntax less"
+ ]
+ },
"prettier": {
"singleQuote": true,
"trailingComma": "all"
@@ -34,7 +91,18 @@
"@commitlint/config-conventional": "^12.1.4",
"@plone/scripts": "*",
"@release-it/conventional-changelog": "^2.0.1",
+ "stylelint": "15.11.0",
+ "stylelint-config-idiomatic-order": "9.0.0",
+ "stylelint-prettier": "4.0.2",
"husky": "^6.0.0",
- "release-it": "^14.10.1"
+ "release-it": "^14.10.1",
+ "jest-css-modules": "^2.1.0",
+ "@babel/eslint-parser": "7.23.3",
+ "@babel/plugin-proposal-export-default-from": "7.18.10"
+ },
+ "peerDependencies": {
+ "@plone/volto": ">=16.0.0-alpha.38",
+ "design-comuni-plone-theme": "*",
+ "design-reack-kit": "*"
}
}
diff --git a/src/components/Blocks/NewsletterSubscribe/Background.jsx b/src/components/Blocks/NewsletterSubscribe/Background.jsx
new file mode 100644
index 0000000..29836cd
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/Background.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import cx from 'classnames';
+import { addAppURL, flattenToAppURL } from '@plone/volto/helpers';
+
+const Background = ({ data, children }) => {
+ return (
+
+ {data.background?.[0] && (
+
+ )}
+ {children}
+
+ );
+};
+
+export default Background;
diff --git a/src/components/Blocks/NewsletterSubscribe/Edit.jsx b/src/components/Blocks/NewsletterSubscribe/Edit.jsx
new file mode 100644
index 0000000..9ed8e70
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/Edit.jsx
@@ -0,0 +1,110 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { useDispatch, useSelector } from 'react-redux';
+import { defineMessages, useIntl } from 'react-intl';
+import { Container } from 'design-react-kit';
+import { SidebarPortal } from '@plone/volto/components';
+import { flattenToAppURL } from '@plone/volto/helpers';
+import { getContent } from '@plone/volto/actions';
+import { TextEditorWidget } from 'design-comuni-plone-theme/components/ItaliaTheme';
+import { useHandleDetachedBlockFocus } from 'design-comuni-plone-theme/helpers/blocks';
+import Sidebar from 'volto-newsletter/components/Blocks/NewsletterSubscribe/Sidebar';
+import Background from 'volto-newsletter/components/Blocks/NewsletterSubscribe/Background';
+import SubscribeButton from 'volto-newsletter/components/Blocks/NewsletterSubscribe/SubscribeButton';
+import './NewsletterSubscribe.scss';
+
+const messages = defineMessages({
+ text: {
+ id: 'Insert text…',
+ defaultMessage: 'Insert text…',
+ },
+ selectChannel: {
+ id: 'Newsletter Block: Select a channel from right sidebar',
+ defaultMessage: 'Blocco Newsletter Block: Seleziona un canale dalla barra a destra',
+ },
+ unsubscribable_channel: {
+ id: 'Newsletter Block: unsubscribable_channel',
+ defaultMessage: 'In canale selezionato non è sottoscrivibile.',
+ },
+});
+
+const Edit = (props) => {
+ const { data, block, selected, ...otherProps } = props;
+ const intl = useIntl();
+ const { selectedField, setSelectedField } = useHandleDetachedBlockFocus(props, 'text');
+
+ const dispatch = useDispatch();
+ const channelID = data.channel?.[0] ? flattenToAppURL(data.channel[0]['@id']) : null;
+ const channel = useSelector((state) => state.content.subrequests['channel_' + channelID]?.data);
+ console.log(channel);
+ useEffect(() => {
+ //load channel data
+ if (!channel && channelID) {
+ dispatch(getContent(channelID, null, 'channel_' + channelID));
+ }
+ }, [data.channel]);
+
+ return (
+ <>
+ {channel && channel.is_subscribable ? (
+
+
+
+
+ {
+ setSelectedField('text');
+ }}
+ />
+
+
+ {
+ setSelectedField('title');
+ }}
+ />
+
+
+
+
+
+ ) : channel && !channel.is_subscribable ? (
+ {intl.formatMessage(messages.unsubscribable_channel)}
+ ) : (
+ {intl.formatMessage(messages.selectChannel)}
+ )}
+
+
+
+ >
+ );
+};
+
+/**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+Edit.propTypes = {
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
+ id: PropTypes.string.isRequired,
+ selected: PropTypes.bool.isRequired,
+ block: PropTypes.string.isRequired,
+};
+
+export default Edit;
diff --git a/src/components/Blocks/NewsletterSubscribe/NewsletterSubscribe.scss b/src/components/Blocks/NewsletterSubscribe/NewsletterSubscribe.scss
new file mode 100644
index 0000000..5bc24e6
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/NewsletterSubscribe.scss
@@ -0,0 +1,93 @@
+.block.newsletter-subscribe {
+ .edit-infos {
+ font-style: italic;
+ text-align: center;
+ padding: 1rem;
+ }
+ .block-content {
+ position: relative;
+ height: auto;
+ padding: 2.5rem 0;
+ text-align: center;
+ background-color: var(--bs-gray-600);
+ color: var(--bs-white);
+ z-index: 0;
+
+ &.with-bg {
+ color: var(--bs-white);
+
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ a {
+ color: var(--bs-white);
+ }
+ }
+
+ @media (max-width: 992px) {
+ /*lg breakpoint*/
+ padding: 2rem 1rem;
+ }
+ }
+
+ .background-image {
+ position: absolute;
+ z-index: -1;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-position: top center;
+ background-repeat: no-repeat;
+ background-size: cover;
+
+ &:after {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(#000, 0.65);
+ content: '';
+ left: 0;
+ top: 0;
+ }
+ }
+
+ .title,
+ .text {
+ p:last-of-type {
+ margin-bottom: 0;
+ }
+
+ h1:last-child,
+ h2:last-child,
+ h3:last-child,
+ h4:last-child,
+ h5:last-child {
+ margin-bottom: 0;
+ }
+
+ h1:first-child,
+ h2:first-child,
+ h3:first-child,
+ h4:first-child,
+ h5:first-child {
+ margin-top: 0;
+ }
+ }
+ .block-text-content {
+ z-index: 0;
+ }
+
+ .slate-editor {
+ [data-slate-placeholder='true'] {
+ opacity: 0.7 !important;
+ }
+ }
+
+ .title,
+ .text {
+ margin-bottom: 2rem;
+ }
+}
diff --git a/src/components/Blocks/NewsletterSubscribe/Sidebar.jsx b/src/components/Blocks/NewsletterSubscribe/Sidebar.jsx
new file mode 100644
index 0000000..08e13d4
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/Sidebar.jsx
@@ -0,0 +1,108 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { Segment } from 'semantic-ui-react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+import { ObjectBrowserWidget, CheckboxWidget, TextWidget } from '@plone/volto/components';
+
+const messages = defineMessages({
+ channel: {
+ id: 'channel',
+ defaultMessage: 'Canale',
+ },
+ backgroundImage: {
+ id: 'backgroundImage',
+ defaultMessage: 'Immagine di sfondo',
+ },
+ showFullWidth: {
+ id: 'show_full_width',
+ defaultMessage: 'A tutta larghezza',
+ },
+ buttonText: {
+ id: 'newsletter_subscribe_cta',
+ defaultMessage: 'Testo del bottone',
+ },
+});
+
+const Sidebar = ({ block, data, onChangeBlock, required }) => {
+ const intl = useIntl();
+
+ useEffect(() => {
+ //set default values
+ onChangeBlock(block, {
+ ...data,
+ showFullWidth: data.showFullWidth === undefined ? true : data.showFullWidth,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+ onChangeBlock(block, { ...data, [id]: value })}
+ />
+
+ onChangeBlock(block, { ...data, [id]: value })}
+ />
+ {
+ onChangeBlock(block, { ...data, [name]: checked });
+ }}
+ />
+
+ {
+ onChangeBlock(block, {
+ ...data,
+ [name]: value,
+ });
+ }}
+ />
+
+
+ );
+};
+
+Sidebar.propTypes = {
+ block: PropTypes.string.isRequired,
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
+ onChangeBlock: PropTypes.func.isRequired,
+ openObjectBrowser: PropTypes.func.isRequired,
+ required: PropTypes.bool,
+};
+
+export default Sidebar;
diff --git a/src/components/Blocks/NewsletterSubscribe/SubscribeButton.jsx b/src/components/Blocks/NewsletterSubscribe/SubscribeButton.jsx
new file mode 100644
index 0000000..f7b06a5
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/SubscribeButton.jsx
@@ -0,0 +1,24 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { useDispatch, useSelector } from 'react-redux';
+import { defineMessages, useIntl } from 'react-intl';
+import { UniversalLink } from '@plone/volto/components';
+import { Button } from 'design-react-kit';
+
+const messages = defineMessages({
+ subscribe: {
+ id: 'Newsletter block: subscribe',
+ defaultMessage: 'Iscriviti',
+ },
+});
+
+const SubscribeButton = ({ channel, data, inEditMode = false }) => {
+ const text = data.buttonText ?? intl.formatMessage(messages.subscribe);
+ return (
+
+ );
+};
+
+export default SubscribeButton;
diff --git a/src/components/Blocks/NewsletterSubscribe/View.jsx b/src/components/Blocks/NewsletterSubscribe/View.jsx
new file mode 100644
index 0000000..d5e8ddf
--- /dev/null
+++ b/src/components/Blocks/NewsletterSubscribe/View.jsx
@@ -0,0 +1,65 @@
+import React, { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useIntl } from 'react-intl';
+import PropTypes from 'prop-types';
+import { Container } from 'design-react-kit';
+import { TextBlockView } from '@plone/volto-slate/blocks/Text';
+import { flattenToAppURL } from '@plone/volto/helpers';
+import { getContent } from '@plone/volto/actions';
+import { checkRichTextHasContent } from 'design-comuni-plone-theme/helpers';
+import Background from 'volto-newsletter/components/Blocks/NewsletterSubscribe/Background';
+import SubscribeButton from 'volto-newsletter/components/Blocks/NewsletterSubscribe/SubscribeButton';
+
+import './NewsletterSubscribe.scss';
+
+const View = ({ data, id }) => {
+ const intl = useIntl();
+ const dispatch = useDispatch();
+ const channelID = data.channel?.[0] ? flattenToAppURL(data.channel[0]['@id']) : null;
+ const channel = useSelector((state) => state.content.subrequests['channel_' + channelID]?.data);
+
+ useEffect(() => {
+ //load channel data
+ if (!channel && channelID) {
+ dispatch(getContent(channelID, null, 'channel_' + channelID));
+ }
+ }, [data.channel]);
+
+ return channel && channel.is_subscribable ? (
+
+
+
+
+ {checkRichTextHasContent(data.title) && (
+
+
+
+ )}
+
+ {checkRichTextHasContent(data.text) && (
+
+
+
+ )}
+
+
+
+
+
+
+ ) : (
+ <>>
+ );
+};
+
+/**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+View.propTypes = {
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
+ id: PropTypes.string.isRequired,
+};
+
+export default View;
diff --git a/src/icons/subscribe.svg b/src/icons/subscribe.svg
new file mode 100644
index 0000000..67ed780
--- /dev/null
+++ b/src/icons/subscribe.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 0e5c30a..dbb000f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,11 +1,4 @@
-export {
- subscribeNewsletter,
- resetSubscribeNewsletter,
- unsubscribeNewsletter,
- resetUnsubscribeNewsletter,
- confirmNewsletterSubscription,
- resetConfirmNewsletterSubscription,
-} from './actions';
+export { subscribeNewsletter, resetSubscribeNewsletter, unsubscribeNewsletter, resetUnsubscribeNewsletter, confirmNewsletterSubscription, resetConfirmNewsletterSubscription } from './actions';
import Channel from './views/Channel';
import Message from './views/Message';
@@ -13,11 +6,11 @@ import NewsletterConfirmSubscribe from './views/NewsletterConfirmSubscribe';
import NewsletterConfirmUnsubscribe from './views/NewsletterConfirmUnsubscribe';
import NewsletterConfirmView from './views/NewsletterConfirmView';
-import {
- subscribeNewsletterReducer,
- unsubscribeNewsletterReducer,
- confirmNewsletterSubscriptionReducer,
-} from './reducers';
+import subscribeSVG from './icons/subscribe.svg';
+import NewsletterSubscribeView from 'volto-newsletter/components/Blocks/NewsletterSubscribe/View';
+import NewsletterSubscribeEdit from 'volto-newsletter/components/Blocks/NewsletterSubscribe/Edit';
+
+import { subscribeNewsletterReducer, unsubscribeNewsletterReducer, confirmNewsletterSubscriptionReducer } from './reducers';
const applyConfig = (config) => {
config.addonReducers = {
@@ -41,14 +34,29 @@ const applyConfig = (config) => {
Message,
};
+ config.blocks.blocksConfig = {
+ ...config.blocks.blocksConfig,
+ 'newsletter-subscribe': {
+ id: 'newsletter-subscribe',
+ title: 'Iscrizione newsletter',
+ icon: subscribeSVG,
+ group: 'newsletter',
+ view: NewsletterSubscribeView,
+ edit: NewsletterSubscribeEdit,
+ restricted: false,
+ mostUsed: false,
+ security: {
+ addPermission: [],
+ view: [],
+ },
+ sidebarTab: 1,
+ },
+ };
+ config.blocks.groupBlocksOrder.push({ id: 'newsletter', title: 'Newsletter' });
+
return config;
};
export default applyConfig;
-export {
- Channel,
- NewsletterConfirmSubscribe,
- NewsletterConfirmUnsubscribe,
- NewsletterConfirmView,
-};
+export { Channel, NewsletterConfirmSubscribe, NewsletterConfirmUnsubscribe, NewsletterConfirmView };
diff --git a/src/views/Channel.jsx b/src/views/Channel.jsx
index 363c4f3..a884c58 100644
--- a/src/views/Channel.jsx
+++ b/src/views/Channel.jsx
@@ -1,22 +1,11 @@
import React, { useState, createRef, useCallback, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
-import { Grid } from 'semantic-ui-react';
import { Form, Label, Input, Button } from 'design-react-kit';
import { useDispatch, useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { Icon } from 'design-comuni-plone-theme/components/ItaliaTheme';
-import {
- SideMenu,
- PageHeader,
- RichTextSection,
- SkipToMainContent,
-} from 'design-comuni-plone-theme/components/ItaliaTheme/View';
-import {
- subscribeNewsletter,
- resetSubscribeNewsletter,
- unsubscribeNewsletter,
- resetUnsubscribeNewsletter,
-} from '../actions';
+import { SideMenu, PageHeader, RichTextSection, SkipToMainContent } from 'design-comuni-plone-theme/components/ItaliaTheme/View';
+import { subscribeNewsletter, resetSubscribeNewsletter, unsubscribeNewsletter, resetUnsubscribeNewsletter } from '../actions';
import HoneypotWidget from './HoneypotWidget/HoneypotWidget';
const messages = defineMessages({
@@ -42,18 +31,15 @@ const messages = defineMessages({
},
newsletterSubscriptionThankyou: {
id: 'newsletterSubscriptionThankyou',
- defaultMessage:
- 'Grazie per esserti iscritto alla newsletter. Controlla la tua casella di posta elettronica per verificare la tua iscrizione.',
+ defaultMessage: 'Grazie per esserti iscritto alla newsletter. Controlla la tua casella di posta elettronica per verificare la tua iscrizione.',
},
newsletterUnsubscriptionConfirmation: {
id: 'newsletterUnsubscriptionConfirmation',
- defaultMessage:
- "La tua richiesta di cancellazione dell'iscrizione alla newsletter è stata inviata. Controlla la tua casella di posta elettronica per verificare l'operazione.",
+ defaultMessage: "La tua richiesta di cancellazione dell'iscrizione alla newsletter è stata inviata. Controlla la tua casella di posta elettronica per verificare l'operazione.",
},
newsletterSubscriptionError: {
id: 'newsletterSubscriptionError',
- defaultMessage:
- "Si è verificato un errore durante l'invio della richiesta. Si prega di riprovare più tardi.",
+ defaultMessage: "Si è verificato un errore durante l'invio della richiesta. Si prega di riprovare più tardi.",
},
user_subscribe_success: {
id: 'user_subscribe_success',
@@ -77,8 +63,7 @@ const messages = defineMessages({
},
unsubscribe_generic: {
id: 'unsubscribe_generic',
- defaultMessage:
- 'Errore durante la richiesta. Si prega di riprovare più tardi.',
+ defaultMessage: 'Errore durante la richiesta. Si prega di riprovare più tardi.',
},
});
@@ -86,23 +71,11 @@ const Channel = ({ content, location }) => {
const intl = useIntl();
const dispatch = useDispatch();
- const {
- loading: subscribeLoading,
- loaded: subscribeLoaded,
- error: subscribeError,
- result: subscribeResult,
- } = useSelector((state) => state.subscribeNewsletter);
- const { status: subscribeStatus, errors: subscribeErrors = [] } =
- subscribeResult ?? {};
+ const { loading: subscribeLoading, loaded: subscribeLoaded, error: subscribeError, result: subscribeResult } = useSelector((state) => state.subscribeNewsletter);
+ const { status: subscribeStatus, errors: subscribeErrors = [] } = subscribeResult ?? {};
- const {
- loading: unsubscribeLoading,
- loaded: unsubscribeLoaded,
- error: unsubscribeError,
- result: unsubscribeResult,
- } = useSelector((state) => state.unsubscribeNewsletter);
- const { status: unsubscribeStatus, errors: unsubscribeErrors = [] } =
- unsubscribeResult ?? {};
+ const { loading: unsubscribeLoading, loaded: unsubscribeLoaded, error: unsubscribeError, result: unsubscribeResult } = useSelector((state) => state.unsubscribeNewsletter);
+ const { status: unsubscribeStatus, errors: unsubscribeErrors = [] } = unsubscribeResult ?? {};
const [email, setEmail] = useState('');
const [unsubEmail, setUnsubEmail] = useState('');
@@ -238,37 +211,17 @@ const Channel = ({ content, location }) => {
<>
-
+
-
-
+
+
{content.is_subscribable && (
)}
-
+