diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 031964adc..73d84fb5a 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,3 +1,7 @@ +## January 10, 2024 + +- **Task** Custom Consent per Engagement [🎟️DESENG-456](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-456) + ## January 9, 2024 - **Task** Improvements from Epic [🎟️DESENG-468](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-468) diff --git a/met-api/migrations/versions/bd0eb0d25caf_adding_engagement_consent_message.py b/met-api/migrations/versions/bd0eb0d25caf_adding_engagement_consent_message.py new file mode 100644 index 000000000..2d2ce9577 --- /dev/null +++ b/met-api/migrations/versions/bd0eb0d25caf_adding_engagement_consent_message.py @@ -0,0 +1,28 @@ +"""adding_engagement_consent_message + +Revision ID: bd0eb0d25caf +Revises: 4114001e1a4c +Create Date: 2024-01-10 12:21:32.781720 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'bd0eb0d25caf' +down_revision = '4114001e1a4c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('engagement', sa.Column('consent_message', postgresql.JSON(astext_type=sa.Text()), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('engagement', 'consent_message') + # ### end Alembic commands ### diff --git a/met-api/src/met_api/models/engagement.py b/met-api/src/met_api/models/engagement.py index 3a5980615..b122a2684 100644 --- a/met-api/src/met_api/models/engagement.py +++ b/met-api/src/met_api/models/engagement.py @@ -48,6 +48,7 @@ class Engagement(BaseModel): status_block = db.relationship('EngagementStatusBlock', backref='engagement') tenant_id = db.Column(db.Integer, db.ForeignKey('tenant.id'), nullable=True) is_internal = db.Column(db.Boolean, nullable=False) + consent_message = db.Column(JSON, unique=False, nullable=True) @classmethod def get_engagements_paginated( @@ -124,6 +125,7 @@ def update_engagement(cls, engagement: EngagementSchema) -> Engagement: content=engagement.get('content', None), rich_content=engagement.get('rich_content', None), is_internal=engagement.get('is_internal', record.is_internal), + consent_message=engagement.get('consent_message', record.consent_message), ) query.update(update_fields) db.session.commit() diff --git a/met-api/src/met_api/schemas/engagement.py b/met-api/src/met_api/schemas/engagement.py index b1961b29e..9ef4db9b0 100644 --- a/met-api/src/met_api/schemas/engagement.py +++ b/met-api/src/met_api/schemas/engagement.py @@ -48,6 +48,7 @@ class Meta: # pylint: disable=too-few-public-methods status_block = fields.List(fields.Nested(EngagementStatusBlockSchema)) tenant_id = fields.Str(data_key='tenant_id') is_internal = fields.Bool(data_key='is_internal') + consent_message = fields.Str(data_key='consent_message') def get_submissions_meta_data(self, obj): """Get the meta data of the submissions made in the survey.""" diff --git a/met-api/src/met_api/services/engagement_service.py b/met-api/src/met_api/services/engagement_service.py index 0c75ab67f..4392da86b 100644 --- a/met-api/src/met_api/services/engagement_service.py +++ b/met-api/src/met_api/services/engagement_service.py @@ -183,7 +183,8 @@ def _create_engagement_model(engagement_data: dict) -> EngagementModel: banner_filename=engagement_data.get('banner_filename', None), content=engagement_data.get('content', None), rich_content=engagement_data.get('rich_content', None), - is_internal=engagement_data.get('is_internal', False) + is_internal=engagement_data.get('is_internal', False), + consent_message=engagement_data.get('consent_message', None) ) new_engagement.save() return new_engagement diff --git a/met-web/src/components/FormCAC/FirstTab.tsx b/met-web/src/components/FormCAC/FirstTab.tsx index 4e4186593..f62c90f87 100644 --- a/met-web/src/components/FormCAC/FirstTab.tsx +++ b/met-web/src/components/FormCAC/FirstTab.tsx @@ -8,6 +8,8 @@ import { useAppTranslation } from 'hooks'; import { FormContext } from './FormContext'; import { TAB_TWO } from './constants'; import { When } from 'react-if'; +import { Editor } from 'react-draft-wysiwyg'; +import { getEditorStateFromRaw } from 'components/common/RichTextEditor/utils'; // Define the Yup schema for validation const schema = yup.object({ @@ -22,7 +24,7 @@ interface FormData { export const FirstTab: React.FC = () => { const { t: translate } = useAppTranslation(); - const { setTabValue, setFormSubmission } = useContext(FormContext); + const { consentMessage, setTabValue, setFormSubmission } = useContext(FormContext); // Initialize form state and validation using react-hook-form const { @@ -86,11 +88,7 @@ export const FirstTab: React.FC = () => { I understand that... - Personal information is collected under Section 26(c) of the Freedom of Information and Protection of - Privacy Act, for the purpose of participating in the Community Advisory Committee conducted by the - Environmental Assessment Office. If you have any questions about the collection, use and disclosure of - your personal information, please contact {translate('cacForm.contactTitle')} at{' '} - {contactEmail}. + diff --git a/met-web/src/components/FormCAC/FormContext.tsx b/met-web/src/components/FormCAC/FormContext.tsx index 4679aaceb..021ed7a59 100644 --- a/met-web/src/components/FormCAC/FormContext.tsx +++ b/met-web/src/components/FormCAC/FormContext.tsx @@ -29,6 +29,7 @@ export interface FormContextProps { loading: boolean; submitting: boolean; setSubmitting: React.Dispatch>; + consentMessage: string; } export const FormContext = createContext({ @@ -52,6 +53,7 @@ export const FormContext = createContext({ setSubmitting: () => { return; }, + consentMessage: '', }); export const FormContextProvider = ({ children }: { children: JSX.Element }) => { const { widgetId, engagementId } = useParams<{ widgetId: string; engagementId: string }>(); @@ -68,6 +70,7 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) => const [submitting, setSubmitting] = useState(false); const [engagement, setEngagement] = useState(null); const [engagementSlug, setEngagementSlug] = useState(''); + const [consentMessage, setConsentMessage] = useState(''); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -116,6 +119,7 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) => const loadData = async () => { const engagement = await loadEngagement(); setEngagement(engagement ?? null); + setConsentMessage(engagement?.consent_message ?? ''); const subscribeWidget = await loadWidget(); verifyData(engagement, subscribeWidget); loadEngagementSlug(); @@ -185,6 +189,7 @@ export const FormContextProvider = ({ children }: { children: JSX.Element }) => loading, submitting, setSubmitting, + consentMessage, }} > {children} diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx index 625e9b822..dfe748780 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/EngagementTabsContext.tsx @@ -22,6 +22,7 @@ interface EngagementFormData { description: string; content: string; is_internal: boolean; + consent_message: string; } interface EngagementSettingsFormData { @@ -35,6 +36,7 @@ const initialEngagementFormData = { description: '', content: '', is_internal: false, + consent_message: '', }; interface EngagementFormError { @@ -139,6 +141,7 @@ export const EngagementTabsContextProvider = ({ children }: { children: React.Re description: savedEngagement.description || '', content: savedEngagement.content || '', is_internal: savedEngagement.is_internal || false, + consent_message: savedEngagement.consent_message || '', }); const [richDescription, setRichDescription] = useState(savedEngagement?.rich_description || ''); const [richContent, setRichContent] = useState(savedEngagement?.rich_content || ''); diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/Settings/ConsentMessage.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/ConsentMessage.tsx new file mode 100644 index 000000000..ef994ac46 --- /dev/null +++ b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/ConsentMessage.tsx @@ -0,0 +1,31 @@ +import React, { useContext } from 'react'; +import { Box, Grid } from '@mui/material'; +import { MetHeader4 } from 'components/common'; +import { EngagementSettingsContext } from './EngagementSettingsContext'; +import RichTextEditor from 'components/common/RichTextEditor'; + +const ConsentMessage = () => { + const { consentMessage, setConsentMessage } = useContext(EngagementSettingsContext); + + const handleRichContentChange = (newState: string) => { + setConsentMessage(newState); + }; + + return ( + + + Collection Notice/Consent Message + + + + + + + + ); +}; + +export default ConsentMessage; diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsContext.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsContext.tsx index 9412c5823..c7bb86e87 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsContext.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsContext.tsx @@ -8,6 +8,8 @@ import { SubmissionStatus } from 'constants/engagementStatus'; export interface EngagementSettingsContextState { isInternal: boolean; setIsInternal: (isInternal: boolean) => void; + consentMessage: string; + setConsentMessage: (richContent: string) => void; sendReport: boolean; setSendReport: (sendReport: boolean) => void; handleSaveSettings: () => void; @@ -20,6 +22,10 @@ export const EngagementSettingsContext = createContext { return; }, + consentMessage: '', + setConsentMessage: () => { + return; + }, sendReport: false, setSendReport: () => { return; @@ -41,6 +47,7 @@ export const EngagementSettingsContextProvider = ({ children }: { children: Reac const [isInternal, setIsInternal] = useState(savedIsInternal); const [sendReport, setSendReport] = useState(Boolean(settings.send_report)); const [updatingSettings, setUpdatingSettings] = useState(false); + const [consentMessage, setConsentMessage] = useState(savedEngagement?.consent_message || ''); const handleUpdateEngagementMetadata = () => { return handleUpdateEngagementMetadataRequest({ @@ -53,6 +60,7 @@ export const EngagementSettingsContextProvider = ({ children }: { children: Reac return handleUpdateEngagementRequest({ ...engagementFormData, is_internal: isInternal, + consent_message: consentMessage, }); }; @@ -89,6 +97,8 @@ export const EngagementSettingsContextProvider = ({ children }: { children: Reac value={{ isInternal, sendReport, + consentMessage, + setConsentMessage, setIsInternal, setSendReport, handleSaveSettings, diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsForm.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsForm.tsx index 19b15506d..d1c5c4605 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsForm.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/Settings/EngagementSettingsForm.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Divider, Grid } from '@mui/material'; import { MetPaper, PrimaryButton } from 'components/common'; import EngagementInformation from './EngagementInformation'; +import ConsentMessage from './ConsentMessage'; import InternalEngagement from './InternalEngagement'; import SendReport from './SendReport'; import { EngagementSettingsContext } from './EngagementSettingsContext'; @@ -34,6 +35,12 @@ const EngagementSettingsForm = () => { + + + + + + Save diff --git a/met-web/src/components/engagement/form/types.ts b/met-web/src/components/engagement/form/types.ts index d11342179..b389dca29 100644 --- a/met-web/src/components/engagement/form/types.ts +++ b/met-web/src/components/engagement/form/types.ts @@ -45,6 +45,7 @@ export interface EngagementFormUpdate { rich_content?: string; is_internal?: boolean; status_block?: EngagementStatusBlock[]; + consent_message?: string; } export type EngagementParams = { diff --git a/met-web/src/components/engagement/view/EmailPanel.tsx b/met-web/src/components/engagement/view/EmailPanel.tsx index 042ecce34..ef9a3f590 100644 --- a/met-web/src/components/engagement/view/EmailPanel.tsx +++ b/met-web/src/components/engagement/view/EmailPanel.tsx @@ -1,4 +1,5 @@ -import React, { FormEvent, useState } from 'react'; +import React, { FormEvent, useContext, useState } from 'react'; +import { ActionContext } from './ActionContext'; import { Grid, Checkbox, TextField, FormControl, FormControlLabel, FormHelperText, Stack, Link } from '@mui/material'; import { EmailPanelProps } from './types'; import { @@ -13,8 +14,11 @@ import { } from 'components/common'; import { When } from 'react-if'; import { INTERNAL_EMAIL_DOMAIN } from 'constants/emailVerification'; +import { Editor } from 'react-draft-wysiwyg'; +import { getEditorStateFromRaw } from 'components/common/RichTextEditor/utils'; const EmailPanel = ({ email, checkEmail, handleClose, updateEmail, isSaving, isInternal }: EmailPanelProps) => { + const { savedEngagement } = useContext(ActionContext); const [checked, setChecked] = useState(false); const [emailFormError, setEmailFormError] = useState({ terms: false, @@ -82,18 +86,11 @@ const EmailPanel = ({ email, checkEmail, handleClose, updateEmail, isSaving, isI - {` - Personal information (your email address) is collected under Section 26(c) and 26(e) of the Freedom of Information\ - and Protection of Privacy Act, for the purpose of providing content updates and future opportunities to participate.\ - Your email is never shared with third parties. - `} -
-
- { - 'If you have any questions about the collection, use and disclosure of your personal information,\ - please contact the Director of Digital Services at ' - } - Sid.Tobias@gov.bc.ca +
void }) => { const dispatch = useAppDispatch(); @@ -106,27 +108,13 @@ const EmailListModal = ({ open, setOpen }: { open: boolean; setOpen: (open: bool handleConfirm={sendEmail} isSaving={isSaving} termsOfService={ - - - Personal information is collected under Section 26(c) of the Freedom of Information and - Protection of Privacy Act, for the purpose of providing content updates and future opportunities - to participate in engagements, as well as for the purpose of providing general feedback to - evaluate engagements conducted by the Environmental Assessment Office. - - - If you have any questions about the collection, use and disclosure of your personal information, - please contact the Director of Digital Services at{' '} - Sid.Tobias@gov.bc.ca - - + + + } header={'Sign Up for Updates'} subText={[ diff --git a/met-web/src/models/engagement.ts b/met-web/src/models/engagement.ts index 47f0b6909..70f6ee069 100644 --- a/met-web/src/models/engagement.ts +++ b/met-web/src/models/engagement.ts @@ -25,6 +25,7 @@ export interface Engagement { submissions_meta_data: SurveySubmissionData; status_block: EngagementStatusBlock[]; is_internal: boolean; + consent_message: string; } export interface Status { @@ -71,6 +72,7 @@ export const createDefaultEngagement = (): Engagement => { }, status_block: [], is_internal: false, + consent_message: '', }; };