Skip to content

Commit

Permalink
Custom newsletter management system
Browse files Browse the repository at this point in the history
Allow user to view and update their subscription status
  • Loading branch information
255kb committed Feb 5, 2024
1 parent 44f3ebe commit 9596d46
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 57 deletions.
62 changes: 28 additions & 34 deletions components/account-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,37 @@ import { useRouter } from 'next/router';
import { FunctionComponent } from 'react';
import { useAuth } from '../utils/auth';

const AccountHeader: FunctionComponent = function () {
const auth = useAuth();
const router = useRouter();
const AccountHeader: FunctionComponent<{ title: string; subtitle: string }> =
function ({ title, subtitle }) {
const auth = useAuth();
const router = useRouter();

const logout = async () => {
await auth.logout();
};

const subtitles = {
info: 'General information',
security: 'Manage your password',
subscription: 'Manage your subscription',
users: 'Manage your plan users'
};
const logout = async () => {
await auth.logout();
};

return (
<header className='bg-dark pt-9 pb-11 d-md-block'>
<div className='container-md'>
<div className='row align-items-center'>
<div className='col'>
<h1 className='fw-bold text-white mb-2'>My account</h1>
<p className='fs-lg text-white text-opacity-75 mb-0'>
{subtitles[router.asPath.split('/')[2]]}
</p>
</div>
<div className='col-auto'>
<button
className='btn btn-sm bg-gray-300 bg-opacity-20 bg-opacity-25-hover text-white'
onClick={async () => logout()}
>
Log Out
</button>
return (
<header className='bg-dark pt-9 pb-11 d-md-block'>
<div className='container-md'>
<div className='row align-items-center'>
<div className='col'>
<h1 className='fw-bold text-white mb-2'>{title}</h1>
<p className='fs-lg text-white text-opacity-75 mb-0'>
{subtitle}
</p>
</div>
<div className='col-auto'>
<button
className='btn btn-sm bg-gray-300 bg-opacity-20 bg-opacity-25-hover text-white'
onClick={async () => logout()}
>
Log Out
</button>
</div>
</div>
</div>
</div>
</header>
);
};
</header>
);
};

export default AccountHeader;
16 changes: 15 additions & 1 deletion components/account-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,21 @@ const AccountMenu: FunctionComponent = function () {
</Link>
</li>
</>
)}{' '}
)}
<li
className={`list-item ${
router.pathname.includes('account/notifications')
? 'active'
: ''
}`}
>
<Link
href='/account/notifications/'
className='list-link text-reset'
>
Notifications
</Link>
</li>
</ul>
</div>
</div>
Expand Down
14 changes: 11 additions & 3 deletions models/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export type User = {
teamRole: TeamRoles;
templatesQuota: number;
templatesQuotaUsed: number;
newsletter?: boolean;
subscription: {
provider: 'stripe' | 'paddle';
renewOn: number;
Expand All @@ -38,6 +37,15 @@ export type User = {
};
};

export type UserProperties = {
[T in keyof User]?: User[T];
export type EmailingContact = {
email: string;
newsletter: boolean;
productUpdates: boolean;
coursePreview: boolean;
};

export type EmailingStatuses = {
newsletter: boolean;
productUpdates: boolean;
coursePreview: boolean;
};
9 changes: 6 additions & 3 deletions pages/account/info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useAuth } from '../../utils/auth';
import { useCurrentUser } from '../../utils/queries';

const meta = {
title: 'My account',
title: 'My account - Overview',
description:
'Manage your Mockoon Cloud account information and subscription details'
};
Expand Down Expand Up @@ -44,7 +44,7 @@ const AccountInfo: FunctionComponent = function () {

{!isAuthLoading && isAuth && (
<>
<AccountHeader />
<AccountHeader title='My account' subtitle='General information' />

<main className='pb-8 pb-md-11 mt-md-n6'>
<div className='container-md'>
Expand Down Expand Up @@ -72,7 +72,10 @@ const AccountInfo: FunctionComponent = function () {
{user &&
user.providerData &&
user.providerData.map((providerData) => (
<div className='d-flex'>
<div
key={providerData.providerId}
className='d-flex'
>
<div className='badge badge-rounded-circle text-bg-success-subtle mt-1 me-4'>
<i className='icon-check'></i>
</div>
Expand Down
210 changes: 210 additions & 0 deletions pages/account/notifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { useMutation } from '@tanstack/react-query';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { FunctionComponent, useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import AccountHeader from '../../components/account-header';
import AccountMenu from '../../components/account-menu';
import LoadingPage from '../../components/loading-page';
import Meta from '../../components/meta';
import Layout from '../../layout/layout';
import { EmailingStatuses } from '../../models/user.model';
import { useAuth } from '../../utils/auth';
import { useCurrentUserEmailing } from '../../utils/queries';

const meta = {
title: 'My account - Notifications',
description: 'Manage your Mockoon Cloud account notification parameters'
};

const AccountNotifications: FunctionComponent = function () {
const { isAuth, user, isLoading: isAuthLoading, getIdToken } = useAuth();
const router = useRouter();
const { data: emailingData } = useCurrentUserEmailing();
const { register: registerFormField, setValue, control } = useForm();
const data = useWatch({ control });

useEffect(() => {
if (!isAuthLoading) {
if (!user) {
router.push('/login/');
} else if (user && !isAuth) {
router.push('/email-verification/');
}
}
}, [isAuthLoading, user, isAuth]);

useEffect(() => {
if (emailingData) {
setValue('newsletter', emailingData.newsletter);
setValue('productUpdates', emailingData.productUpdates);
setValue('coursePreview', emailingData.coursePreview);
}
}, [emailingData]);

const { mutate: updateEmailing } = useMutation({
mutationFn: async (data: {
newsletter: boolean;
productUpdates: boolean;
coursePreview: boolean;
}) => {
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/user/emailing`,
{
body: JSON.stringify(data),
method: 'PUT',
headers: {
Authorization: `Bearer ${await getIdToken()}`,
'Content-Type': 'application/json'
}
}
);

if (response.status !== 204) {
const data = await response.json();

throw new Error(data.message);
}
}
});

useEffect(() => {
if (data) {
updateEmailing(data as EmailingStatuses);
}
}, [data]);

return (
<Layout footerBanner='contact'>
<Meta title={meta.title} description={meta.description} />

{isAuthLoading && <LoadingPage />}

{!isAuthLoading && isAuth && (
<>
<AccountHeader
title='My account'
subtitle='Manage your notifications settings'
/>

<main className='pb-8 pb-md-11 mt-md-n6'>
<div className='container-md'>
<div className='row'>
<div className='col-12 col-md-3'>
<AccountMenu />
</div>
<div className='col-12 col-md-9'>
<div className='card card-bleed shadow-light-lg mb-6'>
<div className='card-header'>
<h4 className='mb-0'>Email communications</h4>
</div>
<div className='card-body'>
<div className='list-group list-group-flush'>
<div className='list-group-item'>
<div className='row align-items-center'>
<div className='col'>
<p className='mb-0'>Account information</p>

<small className='text-gray-700'>
Receive account related information and updates
(e.g. billing, account changes, etc.). You
cannot unsubscribe from these emails.
</small>
</div>
<div className='col-auto'>
<div className='form-check form-switch'>
<input
className='form-check-input'
type='checkbox'
id='newsletter'
readOnly
checked
disabled
/>
</div>
</div>
</div>
</div>

<div className='list-group-item'>
<div className='row align-items-center'>
<div className='col'>
<p className='mb-0'>Newsletter</p>

<small className='text-gray-700'>
Receive our newsletter (once a month or less)
</small>
</div>
<div className='col-auto'>
<div className='form-check form-switch'>
<input
className='form-check-input'
type='checkbox'
id='newsletter'
{...registerFormField('newsletter')}
/>
</div>
</div>
</div>
</div>

<div className='list-group-item'>
<div className='row align-items-center'>
<div className='col'>
<p className='mb-0'>Mockoon Pro updates</p>

<small className='text-gray-700'>
Be notified when new{' '}
<Link href={'/pro/'}>Pro</Link> features are
released
</small>
</div>
<div className='col-auto'>
<div className='form-check form-switch'>
<input
className='form-check-input'
type='checkbox'
id='productUpdates'
{...registerFormField('productUpdates')}
/>
</div>
</div>
</div>
</div>
<div className='list-group-item'>
<div className='row align-items-center'>
<div className='col'>
<p className='mb-0'>Online course release</p>

<small className='text-gray-700'>
Be notified when the{' '}
<Link href={'/course/'}>online course</Link> is
available or updated
</small>
</div>
<div className='col-auto'>
<div className='form-check form-switch'>
<input
className='form-check-input'
type='checkbox'
id='coursePreview'
{...registerFormField('coursePreview')}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</>
)}
</Layout>
);
};

export default AccountNotifications;
4 changes: 2 additions & 2 deletions pages/account/security.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Layout from '../../layout/layout';
import { useAuth } from '../../utils/auth';

const meta = {
title: 'My account',
title: 'My account - security',
description:
'Manage your Mockoon Cloud account information and subscription details'
};
Expand Down Expand Up @@ -63,7 +63,7 @@ const AccountSecurity: FunctionComponent = function () {

{!isAuthLoading && isAuth && (
<>
<AccountHeader />
<AccountHeader title='My account' subtitle='Manage your password' />

<main className='pb-8 pb-md-11 mt-md-n6'>
<div className='container-md'>
Expand Down
4 changes: 2 additions & 2 deletions pages/account/subscribe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Layout from '../../layout/layout';
import { useAuth } from '../../utils/auth';

const meta = {
title: 'My account',
title: 'My account - Subscribe',
description: 'Subscribe to a new plan'
};

Expand All @@ -34,7 +34,7 @@ const AccountSubscribe: FunctionComponent = function () {

{!isAuthLoading && isAuth && (
<>
<AccountHeader />
<AccountHeader title='My account' subtitle='Subscribe to a plan' />
<Plans showFree={false} showTagline={false} />
</>
)}
Expand Down
Loading

0 comments on commit 9596d46

Please sign in to comment.