-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(admin): Allow for admins to send mass newsletter consent emails (#…
…1857) * feat(admin): Add marketing sections Will be used to send and manage marketing emails * feat: Add Form to send newsletter consent email * ui: Form improvements * fix: Build issues * feat: Add email subject to form Will be used if subject is set dynamically to sendgrid
- Loading branch information
1 parent
f3a5aea
commit b1b2036
Showing
14 changed files
with
290 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"admin": { | ||
"marketing": "Маркетинг", | ||
"sendConsentEmail": "Изпращане на емайл за съгласие", | ||
"common": { | ||
"templateId": "Идентификатор на Sendgrid шаблон", | ||
"listId": "Идентифицатор на Sendgrid списък с контакти", | ||
"subject": "Тема на емайл" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"admin": { | ||
"marketing": "Marketing", | ||
"sendConsentEmail": "Send newsletter consent email", | ||
"common": { | ||
"templateId": "ID of Sendgrid template", | ||
"listId": "ID of Sendgrid contact list", | ||
"subject": "Email subject" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
src/components/admin/marketing/EmailConsent/SendEmailConsentForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { Button, Grid, Typography } from '@mui/material' | ||
import { useMutation } from '@tanstack/react-query' | ||
import { AxiosError, AxiosResponse } from 'axios' | ||
import { routes } from 'common/routes' | ||
import FormDatePicker from 'components/common/form/FormDatePicker' | ||
import FormTextField from 'components/common/form/FormTextField' | ||
import GenericForm from 'components/common/form/GenericForm' | ||
import SubmitButton from 'components/common/form/SubmitButton' | ||
import { FormikHelpers } from 'formik' | ||
import { NewsLetterConsentResponse, SendNewsLetterConsent } from 'gql/marketing' | ||
import { useTranslation } from 'next-i18next' | ||
import Link from 'next/link' | ||
import { ApiError } from 'service/apiErrors' | ||
import { useSendConsentEmail } from 'service/marketing' | ||
import { AlertStore } from 'stores/AlertStore' | ||
import * as yup from 'yup' | ||
|
||
export default function SendConsentEmailForm() { | ||
const { t } = useTranslation('marketing') | ||
|
||
const initialValues: SendNewsLetterConsent = { | ||
templateId: '', | ||
listId: '', | ||
subject: '', | ||
dateThreshold: new Date().toISOString(), | ||
} | ||
|
||
const validationSchema: yup.SchemaOf<SendNewsLetterConsent> = yup.object().defined().shape({ | ||
templateId: yup.string().required(), | ||
listId: yup.string().required(), | ||
subject: yup.string().required(), | ||
dateThreshold: yup.string().optional(), | ||
}) | ||
|
||
const mutationFn = useSendConsentEmail() | ||
|
||
const handleError = (e: AxiosError<ApiError>) => { | ||
const error = e.response as AxiosResponse<ApiError> | ||
AlertStore.show(error.data.message, 'error') | ||
} | ||
|
||
const mutation = useMutation< | ||
AxiosResponse<NewsLetterConsentResponse>, | ||
AxiosError<ApiError>, | ||
SendNewsLetterConsent | ||
>({ | ||
mutationFn, | ||
onError: (error) => handleError(error), | ||
onSuccess: (data) => { | ||
const response = data.data | ||
AlertStore.show( | ||
t(`Съобщението беше изпратен успешно на ${response.contactCount} емайла.`), | ||
'success', | ||
) | ||
}, | ||
}) | ||
|
||
async function onSubmit( | ||
values: SendNewsLetterConsent, | ||
formikHelpers: FormikHelpers<SendNewsLetterConsent>, | ||
) { | ||
const data: SendNewsLetterConsent = { | ||
templateId: values.templateId, | ||
listId: values.listId, | ||
subject: values.subject, | ||
dateThreshold: values.dateThreshold, | ||
} | ||
await mutation.mutateAsync(data) | ||
if (mutation.isSuccess && !mutation.isLoading) { | ||
formikHelpers.resetForm({ values: initialValues }) | ||
} | ||
} | ||
|
||
return ( | ||
<Grid container gap={2}> | ||
<Grid item> | ||
<Typography variant="h5" component="h2"> | ||
{t('admin.sendConsentEmail')} | ||
</Typography> | ||
</Grid> | ||
<GenericForm | ||
onSubmit={onSubmit} | ||
initialValues={initialValues} | ||
validationSchema={validationSchema}> | ||
<Grid container item spacing={3} xs={12}> | ||
<Grid item xs={12}> | ||
<FormTextField type="text" label={t('admin.common.templateId')} name="templateId" /> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<FormTextField type="text" label={t('admin.common.listId')} name="listId" /> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<FormTextField type="text" label={t('admin.common.subject')} name="subject" /> | ||
</Grid> | ||
<Grid | ||
container | ||
item | ||
xs={12} | ||
direction={'row'} | ||
justifyContent={'space-between'} | ||
alignItems={'center'}> | ||
<Grid item xs={12} md={6}> | ||
<Typography>Премахване от списък на потребители регистрирани след: </Typography> | ||
</Grid> | ||
<FormDatePicker name="dateThreshold" label="" /> | ||
</Grid> | ||
<Grid item xs={12}> | ||
<SubmitButton label="Изпрати" fullWidth loading={mutation.isLoading} /> | ||
<Link href={routes.admin.marketing.index} passHref> | ||
<Button fullWidth>{t('Откажи')}</Button> | ||
</Link> | ||
</Grid> | ||
</Grid> | ||
</GenericForm> | ||
</Grid> | ||
) | ||
} |
19 changes: 19 additions & 0 deletions
19
src/components/admin/marketing/EmailConsent/SendEmailConsentPage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import AdminContainer from 'components/common/navigation/AdminContainer' | ||
import AdminLayout from 'components/common/navigation/AdminLayout' | ||
import React from 'react' | ||
import SendEmailConsentForm from './SendEmailConsentForm' | ||
import { useTranslation } from 'next-i18next' | ||
import { Container } from '@mui/material' | ||
|
||
export default function SendEmailConsentPage() { | ||
const { t } = useTranslation('marketing') | ||
return ( | ||
<AdminLayout> | ||
<AdminContainer title={t('admin.sendConsentEmail')}> | ||
<Container maxWidth={'sm'} sx={{ py: 5 }}> | ||
<SendEmailConsentForm /> | ||
</Container> | ||
</AdminContainer> | ||
</AdminLayout> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Box, Button, CardContent, Container, Grid, Typography } from '@mui/material' | ||
import AdminContainer from 'components/common/navigation/AdminContainer' | ||
import AdminLayout from 'components/common/navigation/AdminLayout' | ||
import React from 'react' | ||
import { useTranslation } from 'next-i18next' | ||
import Link from 'next/link' | ||
import { marketingCards } from './navigation/marketingCards' | ||
|
||
const colors = ['#0179a8', '#346cb0', '#5f4b8b', '#b76ba3', '#a7c796', '#00a28a', '#3686a0'] | ||
export default function MarketingPage() { | ||
const { t } = useTranslation('marketing') | ||
return ( | ||
<AdminLayout> | ||
<AdminContainer title={t('admin.marketing')}> | ||
<Container maxWidth={false} sx={{ py: 5 }}> | ||
<Grid container spacing={2} rowSpacing={4} px={4} pb={4} mb={2}> | ||
{marketingCards.map(({ label, href, icon: Icon, disabled }, index) => ( | ||
<Grid xs={12} sm={6} md={4} lg={2.4} item key={index}> | ||
<Button | ||
disabled={disabled} | ||
sx={{ | ||
height: 130, | ||
maxWidth: 345, | ||
display: 'flex', | ||
flexDirection: 'column', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
borderRadius: 2, | ||
boxShadow: '0px 2px 4px ' + `${colors[index % colors.length]}9A`, | ||
color: colors[index % colors.length], | ||
transition: 'transform 0.3s ease', | ||
'&:hover': { | ||
transform: 'translateY(-5px)', | ||
boxShadow: '0px 4px 8px ' + `${colors[index % colors.length]}9A`, | ||
backgroundColor: `${colors[index % colors.length]}1A`, | ||
border: '1px solid ' + `${colors[index % colors.length]}9A`, | ||
}, | ||
border: '1px solid ' + `${colors[index % colors.length]}7A`, | ||
}}> | ||
<Link href={href} style={{ textDecoration: 'none', color: 'inherit' }}> | ||
<CardContent> | ||
<Box textAlign="center"> | ||
<Icon fontSize="large" /> | ||
</Box> | ||
<Typography variant="h6" component="h2" textAlign="center" fontWeight="bold"> | ||
{label} | ||
</Typography> | ||
</CardContent> | ||
</Link> | ||
</Button> | ||
</Grid> | ||
))} | ||
</Grid> | ||
</Container> | ||
</AdminContainer> | ||
</AdminLayout> | ||
) | ||
} |
18 changes: 18 additions & 0 deletions
18
src/components/admin/marketing/navigation/marketingCards.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { routes } from 'common/routes' | ||
import ThumbUpAltIcon from '@mui/icons-material/ThumbUpAlt' | ||
import SendIcon from '@mui/icons-material/Send' | ||
|
||
export const marketingCards = [ | ||
{ | ||
label: 'Изпращане на емайл за съгласие', | ||
icon: ThumbUpAltIcon, | ||
href: routes.admin.marketing.newsLetterConsent, | ||
disabled: false, | ||
}, | ||
{ | ||
label: 'Изпращане на маркетинг емайл', | ||
icon: SendIcon, | ||
href: routes.admin.marketing.newsLetterConsent, | ||
disabled: true, | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export type SendMarketingEmail = { | ||
templateId: string | ||
listId: string | ||
subject: string | ||
} | ||
|
||
export type SendNewsLetterConsent = SendMarketingEmail & { | ||
dateThreshold?: string | ||
} | ||
|
||
export type NewsLetterConsentResponse = { | ||
contactCount: number | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import MarketingPage from 'components/admin/marketing/MarketingPage' | ||
import { securedAdminProps } from 'middleware/auth/securedProps' | ||
|
||
export const getServerSideProps = securedAdminProps(['common', 'auth', 'validation', 'marketing']) | ||
|
||
export default MarketingPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import SendEmailConsentPage from 'components/admin/marketing/EmailConsent/SendEmailConsentPage' | ||
import { securedAdminProps } from 'middleware/auth/securedProps' | ||
|
||
export const getServerSideProps = securedAdminProps(['common', 'auth', 'validation', 'marketing']) | ||
|
||
export default SendEmailConsentPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { NewsLetterConsentResponse, SendNewsLetterConsent } from 'gql/marketing' | ||
import { useSession } from 'next-auth/react' | ||
import { authConfig } from './restRequests' | ||
import { endpoints } from './apiEndpoints' | ||
import { apiClient } from './apiClient' | ||
|
||
export function useSendConsentEmail() { | ||
const { data: session } = useSession() | ||
return async (data: SendNewsLetterConsent) => { | ||
return await apiClient.post<NewsLetterConsentResponse>( | ||
endpoints.notifications.sendNewsLetterConsentEmail.url, | ||
data, | ||
authConfig(session?.accessToken), | ||
) | ||
} | ||
} |