Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add read-only email subscription settings #987

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/ui/src/components/App/Nav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ const navigationConfig = computed<
contacts: {
name: 'Contacts',
icon: IHUsers
},
'email-notifications': {
name: 'Email notifications',
icon: IHBell,
hidden: true
}
},
my: {
Expand Down
13 changes: 8 additions & 5 deletions apps/ui/src/components/Ui/Switch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
const enabled = defineModel<boolean>({ required: true });

defineProps<{
title: string;
title?: string;
tooltip?: string;
}>();
</script>
Expand All @@ -26,10 +26,13 @@ defineProps<{
</span>
</Switch>
<SwitchLabel class="text-skin-link truncate flex items-center gap-1">
{{ title }}
<UiTooltip v-if="tooltip" :title="tooltip">
<IH-question-mark-circle class="shrink-0" />
</UiTooltip>
<template v-if="title">
{{ title }}
<UiTooltip v-if="tooltip" :title="tooltip">
<IH-question-mark-circle class="shrink-0" />
</UiTooltip>
</template>
<slot v-else />
</SwitchLabel>
</div>
</SwitchGroup>
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/src/networks/offchain/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ export const USER_QUERY = gql`
farcaster
votesCount
created
emailSubscription {
status
subscriptions
}
}
}
`;
Expand Down
8 changes: 7 additions & 1 deletion apps/ui/src/routes/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import My from '@/views/My.vue';
import Network from '@/views/Network.vue';
import Policy from '@/views/Policy.vue';
import Contacts from '@/views/Settings/Contacts.vue';
import EmailNotifications from '@/views/Settings/EmailNotifications.vue';
import SettingsSpaces from '@/views/Settings/Spaces.vue';
import Settings from '@/views/Settings.vue';
import Site from '@/views/Site.vue';
Expand Down Expand Up @@ -45,7 +46,12 @@ export default [
component: Settings,
children: [
{ path: '', name: 'settings-spaces', component: SettingsSpaces },
{ path: 'contacts', name: 'settings-contacts', component: Contacts }
{ path: 'contacts', name: 'settings-contacts', component: Contacts },
{
path: 'email-notifications',
name: 'settings-email-notifications',
component: EmailNotifications
}
]
},
{
Expand Down
11 changes: 11 additions & 0 deletions apps/ui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { ApiSpace as OffchainApiSpace } from '@/networks/offchain/api/types';
// UI
export type NotificationType = 'error' | 'warning' | 'success';

export type EmailSubscriptionType =
| 'summary'
| 'newProposal'
| 'closedProposal';

export type ProposalState =
| 'pending'
| 'active'
Expand Down Expand Up @@ -296,6 +301,7 @@ export type UserProfile = {
lens: string;
farcaster: string;
votesCount: number;
emailSubscription: EmailSubscription;
};

export type User = {
Expand All @@ -304,6 +310,11 @@ export type User = {
follows?: string[];
} & Partial<UserProfile>;

export type EmailSubscription = {
status: 'NOT_SUBSCRIBED' | 'UNVERIFIED' | 'VERIFIED';
subscriptions: EmailSubscriptionType[];
};

export type UserActivity = {
id: string;
name?: string;
Expand Down
175 changes: 175 additions & 0 deletions apps/ui/src/views/Settings/EmailNotifications.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<script lang="ts" setup>
import { clone } from '@/helpers/utils';
import { getValidator } from '@/helpers/validation';
import { EmailSubscriptionType } from '@/types';

useTitle('Email notifications');

const SUBSCRIBE_FORM_STATE = {
email: '',
subscriptions: []
};

const DEFINITION = {
$async: true,
type: 'object',
title: 'Email subscription',
additionalProperties: false,
required: [],
properties: {
email: {
type: 'string',
format: 'email',
title: 'Email',
maxLength: 256,
examples: ['e.g. [email protected]']
},
subscriptions: {
type: 'array',
title: 'Subscriptions',
items: {
type: 'string',
enum: ['newSummary', 'newProposal', 'closedProposal']
}
}
}
};

const SUBSCRIPTIONS_TYPE = [
{
key: 'summary',
title: 'Weekly summary',
description:
'Get a weekly report detailing the activities in your followed spaces.'
},
{
key: 'newProposal',
title: 'Proposal creation',
description:
'Get informed when a new proposal is submitted in your followed spaces.'
},

{
key: 'closedProposal',
title: 'Proposal closure',
description:
'Get informed when a proposal is closed in your followed spaces.'
}
];

const usersStore = useUsersStore();
const { web3 } = useWeb3();

const form = ref<{
email: string;
subscriptions: EmailSubscriptionType[];
}>(clone(SUBSCRIBE_FORM_STATE));
const formErrors = ref<Record<string, any>>({});
const formValidated = ref(false);
const subscriptions = reactive<Record<EmailSubscriptionType, boolean>>({
summary: true,
newProposal: true,
closedProposal: true
});

const user = computed(() => usersStore.getUser(web3.value.account));

const loading = computed(
() => web3.value.authLoading || usersStore.users[web3.value.account]?.loading
);

function handleCreateSubscriptionClick() {}

function handleResendConfirmationClick() {}

function handleUpdateSubscriptionClick() {}

const formValidator = getValidator(DEFINITION);

watch(
[() => web3.value.account, () => web3.value.authLoading],
async ([account, loading]) => {
if (!account || loading) return;

await usersStore.fetchUser(account);

if (user.value?.emailSubscription?.subscriptions) {
Object.keys(subscriptions).forEach(key => {
subscriptions[key] =
user.value?.emailSubscription?.subscriptions.includes(
key as EmailSubscriptionType
);
});
}
},
{ immediate: true }
);

watchEffect(async () => {
formValidated.value = false;

formErrors.value = await formValidator.validateAsync(form);
formValidated.value = true;
});
</script>

<template>
<UiLabel label="Email notifications" />
<div class="p-4 max-w-[640px]">
<UiLoading v-if="loading" class="block" />
<template
v-else-if="!user || user.emailSubscription?.status === 'NOT_SUBSCRIBED'"
>
<h3 class="text-md leading-6">Receive email notifications</h3>
<div class="mb-3">
Stay updated with the latest and important updates directly on your
inbox.
</div>
<div class="s-box">
<UiInputString
v-model="form.email"
:error="formErrors.email"
:definition="DEFINITION.properties.email"
/>
<UiButton @click="handleCreateSubscriptionClick">
Subscribe now
</UiButton>
</div>
</template>
<template v-else-if="user.emailSubscription?.status === 'UNVERIFIED'">
<h3 class="text-md leading-6">Confirm your email</h3>
<div class="mb-3">
We've sent an email to your email address.
<br />
Please check your inbox and follow the instructions to complete the
process.
</div>
<UiButton @click="handleResendConfirmationClick">
Resend confirmation email
</UiButton>
</template>
<div
v-else-if="user.emailSubscription?.status === 'VERIFIED'"
class="space-y-3"
>
<div>
<h3 class="text-md leading-6">Email notifications</h3>
Choose the notifications you'd like to receive - and those you don't.
</div>
<UiSwitch
v-for="type in SUBSCRIPTIONS_TYPE"
:key="type.key"
v-model="subscriptions[type.key]"
class="gap-2.5 !items-start"
>
<div class="space-y-1 leading-[18px]">
<h4 class="text-base font-normal" v-text="type.title" />
<div class="text-skin-text" v-text="type.description" />
</div>
</UiSwitch>
<UiButton @click="handleUpdateSubscriptionClick">
Update subscriptions
</UiButton>
</div>
</div>
</template>
Loading