Skip to content

Commit

Permalink
Subscribe Form Cards (#1966)
Browse files Browse the repository at this point in the history
  • Loading branch information
djnunez-aot authored Aug 4, 2023
1 parent 545ae41 commit eb0c4da
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 59 deletions.
2 changes: 1 addition & 1 deletion met-api/src/met_api/models/widgets_subscribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def get_all_by_type(cls, type_, widget_id):
return db.session.query(cls).filter_by(type=type_, widget_id=widget_id).all()

@classmethod
def update_widget_events_bulk(cls, update_mappings: list) -> list[WidgetSubscribe]:
def update_widget_subscribes_bulk(cls, update_mappings: list) -> list[WidgetSubscribe]:
"""Save widget subscribe sorting."""
db.session.bulk_update_mappings(WidgetSubscribe, update_mappings)
db.session.commit()
Expand Down
30 changes: 18 additions & 12 deletions met-api/src/met_api/resources/widget_subscribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"""


@cors_preflight('GET, POST, OPTIONS, DELETE')
@cors_preflight('GET, POST, OPTIONS')
@API.route('')
class WidgetSubscribe(Resource):
"""Resource for managing a Widget Subscribe."""
Expand All @@ -59,17 +59,6 @@ def post(widget_id):
except BusinessException as err:
return str(err), err.status_code

@staticmethod
@cross_origin(origins=allowedorigins())
def delete(widget_id, subscribe_id):
"""Delete an subscribe ."""
try:
WidgetSubscribeService().delete_subscribe(subscribe_id, widget_id)
response, status = {}, HTTPStatus.OK
except BusinessException as err:
response, status = str(err), err.status_code
return response, status


@cors_preflight('GET,POST,OPTIONS')
@API.route('/<int:subscribe_id>/items', methods=['GET', 'DELETE', 'OPTIONS'])
Expand Down Expand Up @@ -124,3 +113,20 @@ def patch(widget_id):
return WidgetSubscribeSchema().dump(sort_widget_subscribe), HTTPStatus.OK
except BusinessException as err:
return str(err), err.status_code


@cors_preflight('DELETE')
@API.route('/<int:subscribe_id>', methods=['DELETE'])
class WidgetEvent(Resource):
"""Resource for managing a Widget Events."""

@staticmethod
@cross_origin(origins=allowedorigins())
def delete(widget_id, subscribe_id):
"""Delete an subscribe ."""
try:
WidgetSubscribeService().delete_subscribe(subscribe_id, widget_id)
response, status = {}, HTTPStatus.OK
except BusinessException as err:
response, status = str(err), err.status_code
return response, status
7 changes: 5 additions & 2 deletions met-api/src/met_api/services/widget_subscribe_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,16 @@ def update_subscribe_item(widget_id, subscribe_id, item_id, request_json):
raise BusinessException(
error='Invalid widgets and subscribe',
status_code=HTTPStatus.BAD_REQUEST)

subscribe_item: SubscribeItemsModel = SubscribeItemsModel.find_by_id(
item_id)
if subscribe_item.widget_subscribes_id != subscribe_id:
if subscribe_item.widget_subscribe_id != subscribe_id:
raise BusinessException(
error='Invalid widgets and subscribe',
status_code=HTTPStatus.BAD_REQUEST)
WidgetSubscribeService._update_from_dict(subscribe_item, request_json)

WidgetSubscribeService._update_from_dict(
subscribe_item, request_json)
subscribe_item.commit()
return SubscribeItemsModel.find_by_id(item_id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import { Subscribe_TYPE, SubscribeForm } from 'models/subscription';
import RichTextEditor from 'components/common/RichTextEditor';
import { openNotification } from 'services/notificationService/notificationSlice';
import { getTextFromDraftJsContentState } from 'components/common/RichTextEditor/utils';
import { postSubscribeForm } from 'services/subscriptionService';
import { patchSubscribeForm, postSubscribeForm, PatchSubscribeProps } from 'services/subscriptionService';

const schema = yup
.object({
description: yup.string(),
call_to_action_type: yup.string(),
call_to_action_text: yup
.string()
Expand All @@ -39,23 +38,54 @@ const EmailListDrawer = () => {
richEmailListDescription,
setRichEmailListDescription,
setSubscribe,
subscribeToEdit,
loadSubscribe,
} = useContext(SubscribeContext);
const [isCreating, setIsCreating] = useState(false);
const [initialRichDescription, setInitialRichDescription] = useState('');
const subscribeItem = subscribeToEdit ? subscribeToEdit.subscribe_items[0] : null;
const dispatch = useAppDispatch();
const methods = useForm<EmailList>({
resolver: yupResolver(schema),
});

useEffect(() => {
methods.setValue('description', '');
methods.setValue('call_to_action_type', 'link');
methods.setValue('call_to_action_text', 'Click here to sign up');
setInitialRichDescription(richEmailListDescription);
}, []);

useEffect(() => {
methods.setValue('call_to_action_type', subscribeItem ? subscribeItem.call_to_action_type : 'link');
methods.setValue(
'call_to_action_text',
subscribeItem ? subscribeItem.call_to_action_text : 'Click here to sign up',
);
setInitialRichDescription(subscribeItem ? subscribeItem.description : richEmailListDescription);
}, [subscribeToEdit]);

const { handleSubmit } = methods;

const updateEmailListForm = async (data: EmailList) => {
const validatedData = await schema.validate(data);
const { call_to_action_type, call_to_action_text } = validatedData;
if (subscribeToEdit && subscribeItem && widget) {
const subscribeUpdatesToPatch = {
description: richEmailListDescription,
call_to_action_type: call_to_action_type,
call_to_action_text: call_to_action_text,
} as PatchSubscribeProps;

await patchSubscribeForm(widget.id, subscribeToEdit.id, subscribeItem.id, {
...subscribeUpdatesToPatch,
});

loadSubscribe();

dispatch(openNotification({ severity: 'success', text: 'EmailListForm was successfully updated' }));
}
};

const createEmailListForm = async (data: EmailList) => {
const validatedData = await schema.validate(data);
const { call_to_action_type, call_to_action_text } = validatedData;
Expand All @@ -73,12 +103,18 @@ const EmailListDrawer = () => {
],
});

setSubscribe((prevWidgetForms: SubscribeForm[]) => [...prevWidgetForms, createdWidgetForm]);
setSubscribe((prevWidgetForms: SubscribeForm[]) => {
const filteredForms = prevWidgetForms.filter((form) => form.type !== Subscribe_TYPE.EMAIL_LIST);
return [...filteredForms, createdWidgetForm];
});
}
dispatch(openNotification({ severity: 'success', text: 'Email list form was successfully created' }));
};

const saveForm = async (data: EmailList) => {
if (subscribeToEdit) {
return updateEmailListForm(data);
}
return createEmailListForm(data);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
import { useAppDispatch } from 'hooks';
import { WidgetDrawerContext } from '../WidgetDrawerContext';
import { Widget, WidgetType } from 'models/widget';
import { Subscribe, Subscribe_TYPE, SubscribeTypeLabel, SubscribeForm } from 'models/subscription';
import { Subscribe_TYPE, SubscribeTypeLabel, SubscribeForm } from 'models/subscription';
import { getSubscriptionsForms, sortWidgetSubscribeForms } from 'services/subscriptionService';
import { openNotification } from 'services/notificationService/notificationSlice';

Expand All @@ -19,7 +19,7 @@ export interface SubscribeContextProps {
setSubscribe: React.Dispatch<React.SetStateAction<SubscribeForm[]>>;
setSubscribeToEdit: React.Dispatch<React.SetStateAction<SubscribeForm | null>>;
handleSubscribeDrawerOpen: (_Subscribe: SubscribeTypeLabel, _open: boolean) => void;
updateWidgetSubscribeSorting: (widget_Subscribe: Subscribe[]) => void;
updateWidgetSubscribeSorting: (widget_Subscribe: SubscribeForm[]) => void;
richEmailListDescription: string;
setRichEmailListDescription: React.Dispatch<React.SetStateAction<string>>;
richFormSignUpDescription: string;
Expand Down Expand Up @@ -51,7 +51,7 @@ export const SubscribeContext = createContext<SubscribeContextProps>({
handleSubscribeDrawerOpen: (_Subscribe: SubscribeTypeLabel, _open: boolean) => {
/* empty default method */
},
updateWidgetSubscribeSorting: (widget_Subscribe: Subscribe[]) => {
updateWidgetSubscribeSorting: (widget_Subscribe: SubscribeForm[]) => {
/* empty default method */
},
richEmailListDescription: '',
Expand Down Expand Up @@ -104,7 +104,7 @@ export const SubscribeProvider = ({ children }: { children: JSX.Element | JSX.El
loadSubscribe();
}, [widget]);

const updateWidgetSubscribeSorting = async (resortedWidgetSubscribe: Subscribe[]) => {
const updateWidgetSubscribeSorting = async (resortedWidgetSubscribe: SubscribeForm[]) => {
if (!widget) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { WidgetDrawerContext } from '../WidgetDrawerContext';
import { SubscribeContext } from './SubscribeContext';
import { Subscribe_TYPE } from 'models/subscription';
import { WidgetTitle } from '../WidgetTitle';
import { When } from 'react-if';
import SubscribeInfoBlock from './SubscribeInfoBlock';

const Form = () => {
const { handleWidgetDrawerOpen } = useContext(WidgetDrawerContext);
const { handleSubscribeDrawerOpen, widget } = useContext(SubscribeContext);
const { handleSubscribeDrawerOpen, subscribe, widget } = useContext(SubscribeContext);
const subscribeFormExists = subscribe.length > 0;

if (!widget) {
return null;
Expand All @@ -20,31 +23,39 @@ const Form = () => {
<WidgetTitle widget={widget} />
<Divider sx={{ marginTop: '1em' }} />
</Grid>
<When condition={!subscribeFormExists}>
<Grid item xs={12} container direction="row" spacing={1} justifyContent={'flex-start'}>
<Grid item xs={12}>
<MetParagraph>
The email list will collect email addresses for a mailing list. A "double-opt-in" email will
be sent to confirm the subscription. Only the email addresses that have been double-opted-in
will be on the list.
</MetParagraph>
</Grid>
<Grid item xs={12} marginBottom="1em">
<MetParagraph>
The form sign-up will open the pre-defined form. The text and CTA for both are customizable.
</MetParagraph>
</Grid>
</Grid>
</When>
<Grid item>
<WidgetButton onClick={() => handleSubscribeDrawerOpen(Subscribe_TYPE.EMAIL_LIST, true)}>
Email List
</WidgetButton>
</Grid>
<Grid item>
<WidgetButton onClick={() => handleSubscribeDrawerOpen(Subscribe_TYPE.FORM, true)}>
Form Sign-up
</WidgetButton>
</Grid>

<Grid item xs={12} container direction="row" spacing={1} justifyContent={'flex-start'}>
<When condition={subscribeFormExists}>
<Grid item xs={12}>
<MetParagraph>
The email list will collect email addresses for a mailing list. A "double-opt-in" email will be
sent to confirm the subscription. Only the email addresses that have been double-opted-in will
be on the list.
</MetParagraph>
</Grid>
<Grid item xs={12} marginBottom="1em">
<MetParagraph>
The form sign-up will open the pre-defined form. The text and CTA for both are customizable.
</MetParagraph>
</Grid>
<Grid item>
<WidgetButton onClick={() => handleSubscribeDrawerOpen(Subscribe_TYPE.EMAIL_LIST, true)}>
Email List
</WidgetButton>
</Grid>
<Grid item>
<WidgetButton onClick={() => handleSubscribeDrawerOpen(Subscribe_TYPE.FORM, true)}>
Form Sign-up
</WidgetButton>
<SubscribeInfoBlock />
</Grid>
</Grid>
</When>

<Grid item xs={12} container direction="row" spacing={1} justifyContent={'flex-start'} marginTop="2em">
<Grid item>
<PrimaryButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React, { useContext, useRef } from 'react';
import { Grid, Skeleton } from '@mui/material';
import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { MetDraggable, MetDroppable } from 'components/common/Dragdrop';
import { reorder } from 'utils';
import SubscribeInfoPaper from './SubscribeInfoPaper';
import { When } from 'react-if';
import { debounce } from 'lodash';
import { deleteSubscribeForm } from 'services/subscriptionService';
import { useAppDispatch } from 'hooks';
import { openNotificationModal } from 'services/notificationModalService/notificationModalSlice';
import { openNotification } from 'services/notificationService/notificationSlice';
import { SubscribeContext } from './SubscribeContext';
import { Subscribe_TYPE, SubscribeForm } from 'models/subscription';

const SubscribeInfoBlock = () => {
const { subscribe, setSubscribe, isLoadingSubscribe, updateWidgetSubscribeSorting, widget } =
useContext(SubscribeContext);
const dispatch = useAppDispatch();
const debounceUpdateWidgetSubscribeSorting = useRef(
debounce((widgetSubscribeToSort: SubscribeForm[]) => {
updateWidgetSubscribeSorting(widgetSubscribeToSort);
}, 800),
).current;

const moveSubscribeForm = (result: DropResult) => {
if (!result.destination) {
return;
}

const items = reorder(subscribe, result.source.index, result.destination.index);

setSubscribe(items);

debounceUpdateWidgetSubscribeSorting(items);
};

if (isLoadingSubscribe) {
return (
<Grid container direction="row" alignItems={'flex-start'} justifyContent="flex-start" spacing={2}>
<Grid item xs={12}>
<Skeleton variant="rectangular" width="100%" height="12em" />
</Grid>
</Grid>
);
}

const handleRemoveSubscribeForm = (subscribeFormId: number) => {
dispatch(
openNotificationModal({
open: true,
data: {
header: 'Remove SubscribeForm',
subText: [
{
text: 'You will be removing this subscribeForm from the engagement.',
},
{
text: 'Do you want to remove this subscribeForm?',
},
],
handleConfirm: () => {
removeSubscribeForm(subscribeFormId);
},
},
type: 'confirm',
}),
);
};

const removeSubscribeForm = async (subscribeFormId: number) => {
try {
if (widget) {
await deleteSubscribeForm(widget.id, subscribeFormId);
const newSubscribe = subscribe.filter((subscribeForm) => subscribeForm.id !== subscribeFormId);
setSubscribe([...newSubscribe]);
dispatch(openNotification({ severity: 'success', text: 'The subscribeForm was removed successfully' }));
}
} catch (error) {
dispatch(
openNotification({ severity: 'error', text: 'An error occurred while trying to remove subscribeForm' }),
);
}
};

return (
<DragDropContext onDragEnd={moveSubscribeForm}>
<MetDroppable droppableId="droppable">
<Grid container direction="row" alignItems={'flex-start'} justifyContent="flex-start" spacing={2}>
{subscribe.map((subscribeForm: SubscribeForm, index) => {
return (
<Grid item xs={12} key={`Grid-${subscribeForm.widget_id}`}>
<MetDraggable draggableId={String(subscribeForm.widget_id)} index={index}>
<When condition={subscribeForm.type === Subscribe_TYPE.EMAIL_LIST}>
<SubscribeInfoPaper
removeSubscribeForm={handleRemoveSubscribeForm}
subscribeForm={subscribeForm}
/>
</When>
<When condition={subscribeForm.type === Subscribe_TYPE.FORM}>
<SubscribeInfoPaper
removeSubscribeForm={handleRemoveSubscribeForm}
subscribeForm={subscribeForm}
/>
</When>
</MetDraggable>
</Grid>
);
})}
</Grid>
</MetDroppable>
</DragDropContext>
);
};

export default SubscribeInfoBlock;
Loading

0 comments on commit eb0c4da

Please sign in to comment.