From 9aab29536e0a5b3c18aa1cc211785f4c3c97fa7c Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Fri, 28 Oct 2022 17:23:55 -0700 Subject: [PATCH 1/8] api integration done --- frontend/packages/client/.env.example | 7 ++- .../client/src/api/notificationService.js | 33 ++++++------ .../CommunitiesList.js | 8 ++- .../EmailAddressInput.js | 14 +++-- .../NotificationSettingsSection/index.js | 30 +++++++++-- .../src/contexts/NotificationService.js | 51 +++++++++---------- frontend/packages/client/src/pages/Home.js | 1 + .../packages/client/src/pages/Settings.js | 13 ++--- 8 files changed, 97 insertions(+), 60 deletions(-) diff --git a/frontend/packages/client/.env.example b/frontend/packages/client/.env.example index 3335d86a1..a4ebfad74 100644 --- a/frontend/packages/client/.env.example +++ b/frontend/packages/client/.env.example @@ -5,4 +5,9 @@ REACT_APP_BACK_END_SERVER_API=http://localhost:5001 REACT_APP_IPFS_GATEWAY=https://dappercollectives.mypinata.cloud/ipfs REACT_APP_TX_OPTIONS_ADDRS="0xc590d541b72f0ac1,0x72d401812f579e3e" REACT_APP_HOTJAR_SITE_ID=0 -REACT_APP_SENTRY_URL=REPLACE_SENTRY_URL \ No newline at end of file +REACT_APP_SENTRY_URL=REPLACE_SENTRY_URL +REACT_APP_LEANPLUM_APP_ID=app_DYMGKbyTTOtOfoQBJBJ6teLJI4usmMAlHX1CQ13TsuI +REACT_APP_LEANPLUM_DEV_KEY=dev_hMz1c4Irx8BQ6pBUZh5r3R5xJexNmCarsAXdSHrDvR8 +REACT_APP_LEANPLUM_PROD_KEY=prod_Ib9x94ei9fdFTCtpB34Wvueyq5fkbaJh4bbHMNihFvg +REACT_APP_LEANPLUM_EXPORT_KEY=exp_DhDP1lUDXBL52FSp3J0JGEEdGDMqsjZcGBzPDhMW9uY +REACT_APP_LEANPLUM_CRO_KEY=cro_3BW38MKRcTJwqWOcezlYKL5tKKQdmiy3HuMYIaukgIo \ No newline at end of file diff --git a/frontend/packages/client/src/api/notificationService.js b/frontend/packages/client/src/api/notificationService.js index d0ecbabf4..a29908ecc 100644 --- a/frontend/packages/client/src/api/notificationService.js +++ b/frontend/packages/client/src/api/notificationService.js @@ -13,7 +13,7 @@ const options = { headers: { accept: 'application/json' }, }; -export const startLeanplum = () => { +export const startLeanplumForUser = (walletId) => { const IS_LOCAL_DEV = process.env.REACT_APP_APP_ENV === 'development'; if (IS_LOCAL_DEV) { @@ -21,16 +21,16 @@ export const startLeanplum = () => { } else { Leanplum.setAppIdForProductionMode(LEANPLUM_APP_ID, LEANPLUM_PROD_KEY); } - - Leanplum.start(); -}; - -export const setUserId = async (walletId) => { - try { - Leanplum.setUserId(walletId); - } catch (e) { - throw new Error(e); - } + return new Promise((resolve, reject) => { + Leanplum.start((success) => { + Leanplum.setUserId(walletId); + if (success) { + resolve('leanplum user set'); + } else { + reject('leanplum user not set'); + } + }); + }); }; export const getUserSettings = async (walletId) => { @@ -52,10 +52,13 @@ export const getUserSettings = async (walletId) => { const res = { email: data.userAttributes.email, }; - let isSubscribedToCommunityUpdates = true; + let isSubscribedFromCommunityUpdates = true; for (const property in data.userAttributes) { - if (property.includes('community')) { + if ( + property.includes('community') && + typeof data.userAttributes[property] == 'string' + ) { const communityId = property.split('community')[1]; communitySubscriptions.push({ communityId, @@ -69,12 +72,12 @@ export const getUserSettings = async (walletId) => { if (data.unsubscribeCategories) { data.unsubscribeCategories.forEach((category) => { if (parseInt(category.id) === COMMUNITY_UPDATES_CATEGORY_ID) { - isSubscribedToCommunityUpdates = false; + isSubscribedFromCommunityUpdates = false; } }); } - res.isSubscribedToCommunityUpdates = isSubscribedToCommunityUpdates; + res.isSubscribedFromCommunityUpdates = isSubscribedFromCommunityUpdates; return res; } catch (e) { throw new Error(e); diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js index a4fb159ad..9cf9f6a0a 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js @@ -38,9 +38,13 @@ function CommunityListItem({ subscribed, handleUpdateCommunitySubscription, }) { - const { data: community, isLoading } = useCommunityDetails(communityId); + const { + data: community, + isLoading, + error, + } = useCommunityDetails(communityId); const { name, logo, slug } = community ?? {}; - if (isLoading) return null; + if (isLoading || error) return null; return (
  • diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/EmailAddressInput.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/EmailAddressInput.js index ff3636f36..e0b42fae6 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/EmailAddressInput.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/EmailAddressInput.js @@ -5,10 +5,10 @@ import { EMAIL_REGEX } from 'const'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; -export default function EmailAddressInput({ email, setUserEmail }) { - const { register, handleSubmit, formState } = useForm({ +export default function EmailAddressInput({ defaultEmail, setUserEmail }) { + const { register, handleSubmit, formState, setValue } = useForm({ defaultValues: { - email: email, + email: defaultEmail, }, resolver: yupResolver( yup.object().shape({ @@ -20,8 +20,12 @@ export default function EmailAddressInput({ email, setUserEmail }) { }) ), }); - const onSubmit = ({ email }) => { - setUserEmail(email); + const onSubmit = async ({ email }) => { + try { + await setUserEmail(email); + } catch (e) { + setValue('email', defaultEmail); + } }; const { isSubmitting, errors, isDirty } = formState; diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js index dd205b7a0..34fe1f918 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js @@ -1,4 +1,4 @@ -import { Fragment } from 'react'; +import { useErrorHandlerContext } from 'contexts/ErrorHandler'; import { useNotificationServiceContext } from 'contexts/NotificationService'; import CommunitiesList from './CommunitiesList'; import EmailAddressInput from './EmailAddressInput'; @@ -13,6 +13,21 @@ export default function NotificationSettingsSection() { } = useNotificationServiceContext(); const { communitySubscription, email, isSubscribedFromCommunityUpdates } = notificationSettings; + const { notifyError } = useErrorHandlerContext(); + + const handleError = (fn) => { + return async (...args) => { + try { + await fn(...args); + } catch { + const error = new Error( + 'Something went wrong, and your action could not be completed. Please try again later.' + ); + notifyError(error); + throw error; + } + }; + }; return (

    Notification Settings

    @@ -27,19 +42,24 @@ export default function NotificationSettingsSection() { )} {communitySubscription.length > 0 && ( <> - +

    {isSubscribedFromCommunityUpdates && ( )}

    diff --git a/frontend/packages/client/src/contexts/NotificationService.js b/frontend/packages/client/src/contexts/NotificationService.js index 4a1a102ff..9137aec87 100644 --- a/frontend/packages/client/src/contexts/NotificationService.js +++ b/frontend/packages/client/src/contexts/NotificationService.js @@ -1,14 +1,22 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { subscribeNotificationIntentions } from 'const'; +import { + getUserSettings as getUser, + setUserEmail as setEmail, + startLeanplumForUser, + subscribeCommunity, + subscribeToEmailNotifications, + unsubscribeCommunity, + unsubscribeFromEmailNotifications, +} from 'api/notificationService'; import { useWebContext } from './Web3'; const NotificationServiceContext = createContext({}); const INIT_NOTIFICATION_SETTINGS = { - walletId: '', email: '', - communitySubscription: [{ communityId: '1', subscribed: true }], - isSubscribedFromCommunityUpdates: true, + communitySubscription: [], + isSubscribedFromCommunityUpdates: false, }; const updateCommunitySubscriptionState = ( @@ -50,29 +58,21 @@ const NotificationServiceProvider = ({ children }) => { useEffect(() => { if (addr) { (async () => { - await setUserID(addr); - getUserSettings(); + try { + await startLeanplumForUser(addr); + await getUserSettings(); + } catch (e) { + console.log(e); + } })(); } else { setNotificationSettings(INIT_NOTIFICATION_SETTINGS); } }, [addr]); - const setUserID = async (walletId) => { - try { - //here we call api to init the leanplum sdk - setNotificationSettings((prevState) => ({ - ...prevState, - walletId, - })); - } catch { - throw new Error('cannot set user id for leanplum'); - } - }; - const setUserEmail = async (email) => { try { - //here we call api + await setEmail(email); setNotificationSettings((prevState) => ({ ...prevState, email, @@ -84,13 +84,13 @@ const NotificationServiceProvider = ({ children }) => { const getUserSettings = async () => { try { - //here we call api - const { communitySubscription, isSubscribedFromCommunityUpdates } = - INIT_NOTIFICATION_SETTINGS; + const { communitySubscription, isSubscribedFromCommunityUpdates, email } = + await getUser(addr); setNotificationSettings((prevState) => ({ ...prevState, communitySubscription, isSubscribedFromCommunityUpdates, + email, })); } catch { throw new Error('cannot get user settings'); @@ -103,11 +103,11 @@ const NotificationServiceProvider = ({ children }) => { ) => { try { if (subscribeIntention === subscribeNotificationIntentions.subscribe) { - //call api to subscribe community + await subscribeCommunity(communityId); } else if ( subscribeIntention === subscribeNotificationIntentions.unsubscribe ) { - //call api to unsubscribe community + await unsubscribeCommunity(communityId); } setNotificationSettings((prevState) => { const newCommunitySubscription = updateCommunitySubscriptionState( @@ -127,11 +127,11 @@ const NotificationServiceProvider = ({ children }) => { const updateAllEmailNotificationSubscription = async (subscribeIntention) => { if (subscribeIntention === subscribeNotificationIntentions.resubscribe) { - //call api to resubscribe all email notifications + subscribeToEmailNotifications(addr); } else if ( subscribeIntention === subscribeNotificationIntentions.unsubscribe ) { - //call api to unsubscribe all email notifications + unsubscribeFromEmailNotifications(addr); } setNotificationSettings((prevState) => ({ ...prevState, @@ -142,7 +142,6 @@ const NotificationServiceProvider = ({ children }) => { const providerProps = { notificationSettings, - setUserID, setUserEmail, getUserSettings, updateCommunitySubscription, diff --git a/frontend/packages/client/src/pages/Home.js b/frontend/packages/client/src/pages/Home.js index 6ffcfe452..590facfbf 100644 --- a/frontend/packages/client/src/pages/Home.js +++ b/frontend/packages/client/src/pages/Home.js @@ -38,6 +38,7 @@ export default function HomePage() { // missing fields isComingSoon: datum.isComingSoon || false, })); + const browserName = useBrowserName(); const [showToolTip, setValue] = useLocalStorage('dw-safary-tooltip', null); diff --git a/frontend/packages/client/src/pages/Settings.js b/frontend/packages/client/src/pages/Settings.js index 64fb0c2e5..38354f182 100644 --- a/frontend/packages/client/src/pages/Settings.js +++ b/frontend/packages/client/src/pages/Settings.js @@ -1,4 +1,4 @@ -import { useNotificationServiceContext } from 'contexts/NotificationService'; +import { useWebContext } from 'contexts/Web3'; import { HomeFooter } from 'components'; import { ConnectWalletPrompt, @@ -8,19 +8,20 @@ import { import SectionContainer from 'layout/SectionContainer'; export default function Settings() { - const { notificationSettings } = useNotificationServiceContext(); - const { walletId } = notificationSettings; + const { + user: { addr }, + } = useWebContext(); return (

    - {walletId && ( + {addr && (
    - +
    )} - {!walletId && } + {!addr && }
    From 2574dcf8c50f3f58328abc6f7f23bfc73eb0eab7 Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Mon, 31 Oct 2022 13:45:34 -0700 Subject: [PATCH 2/8] finish implementation --- .../client/src/api/notificationService.js | 31 +++++++----- .../CommunitiesList.js | 27 +++++++++-- .../components/modals/Notifications/SignUp.js | 4 +- .../components/modals/Notifications/index.js | 37 +++++++++----- .../src/contexts/NotificationService.js | 48 +++---------------- 5 files changed, 78 insertions(+), 69 deletions(-) diff --git a/frontend/packages/client/src/api/notificationService.js b/frontend/packages/client/src/api/notificationService.js index a29908ecc..3bee957a6 100644 --- a/frontend/packages/client/src/api/notificationService.js +++ b/frontend/packages/client/src/api/notificationService.js @@ -5,6 +5,7 @@ import { LEANPLUM_EXPORT_KEY, LEANPLUM_PROD_KEY, } from 'api/constants'; +import { subscribeNotificationIntentions } from 'const'; import Leanplum from 'leanplum-sdk'; const COMMUNITY_UPDATES_CATEGORY_ID = 1; @@ -13,6 +14,22 @@ const options = { headers: { accept: 'application/json' }, }; +const getDesiredAttributes = (communitySubIntentions) => { + //subscribeUpdateIntentions = [{communityId:"1", subscribeIntention:"subscribe"},{communityId:"2",subscribeIntention:"unsubscribe"}] + return communitySubIntentions + .map(({ communityId, subscribeIntention }) => ({ + key: `community${communityId}`, + value: + subscribeIntention === subscribeNotificationIntentions.subscribe + ? 'True' + : 'False', + })) + .reduce((acc, curr) => { + const { key, value } = curr; + acc[key] = value; + return acc; + }, {}); +}; export const startLeanplumForUser = (walletId) => { const IS_LOCAL_DEV = process.env.REACT_APP_APP_ENV === 'development'; @@ -93,18 +110,10 @@ export const setUserEmail = async (email) => { } }; -export const unsubscribeCommunity = async (communityId) => { - try { - Leanplum.setUserAttributes({ [`community${communityId}`]: 'False' }); - return true; - } catch (e) { - throw new Error(e); - } -}; - -export const subscribeCommunity = async (communityId) => { +export const updateCommunitySubscription = async (communitySubIntentions) => { + const desiredAttributes = getDesiredAttributes(communitySubIntentions); try { - Leanplum.setUserAttributes({ [`community${communityId}`]: 'True' }); + Leanplum.setUserAttributes(desiredAttributes); return true; } catch (e) { throw new Error(e); diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js index 9cf9f6a0a..f10d9f08a 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js @@ -1,16 +1,37 @@ import Blockies from 'react-blockies'; -import { useCommunityDetails } from 'hooks'; +import { useWebContext } from 'contexts/Web3'; +import { useCommunityDetails, useUserCommunities } from 'hooks'; import { subscribeNotificationIntentions } from 'const'; export default function CommunitiesList({ communitySubscription, updateCommunitySubscription, }) { + const { + user: { addr }, + } = useWebContext(); + const { data: userCommunities, loading: isLoadingUserCommunities } = + useUserCommunities({ + addr, + count: 100, + initialLoading: false, + }); + const displayCommunities = isLoadingUserCommunities + ? [] + : (userCommunities || []).map(({ id }) => { + const isSubscribed = communitySubscription.find( + ({ communityId }) => communityId === id.toString() + )?.subscribed; + return { + communityId: id.toString(), + subscribed: !!isSubscribed, + }; + }); const handleUpdateCommunitySubscription = (communityId, subscribed) => { const subscribeIntention = subscribed ? subscribeNotificationIntentions.unsubscribe : subscribeNotificationIntentions.subscribe; - updateCommunitySubscription(communityId, subscribeIntention); + updateCommunitySubscription([{ communityId, subscribeIntention }]); }; return (
    @@ -18,7 +39,7 @@ export default function CommunitiesList({ Edit which communities you want notifications from:
      - {communitySubscription.map(({ communityId, subscribed }) => ( + {displayCommunities.map(({ communityId, subscribed }) => ( { +const SignUpForm = ({ setErrorMessage, onSubscribe, onClose, userEmail }) => { const [signupAll, setSignupAll] = useState(false); const { register, handleSubmit, formState } = useForm({ resolver: yupResolver(getSchema()), defaultValues: { - email: '', + email: userEmail, }, }); diff --git a/frontend/packages/client/src/components/modals/Notifications/index.js b/frontend/packages/client/src/components/modals/Notifications/index.js index fcd0fb699..a75793c3b 100644 --- a/frontend/packages/client/src/components/modals/Notifications/index.js +++ b/frontend/packages/client/src/components/modals/Notifications/index.js @@ -1,23 +1,37 @@ import { useNotificationServiceContext } from 'contexts/NotificationService'; +import { useWebContext } from 'contexts/Web3'; +import { useUserCommunities } from 'hooks'; import { subscribeNotificationIntentions } from 'const'; import NotificationsSignUp from './SignUp'; const NotificationsModal = ({ onClose, communityId }) => { + const { updateCommunitySubscription, notificationSettings } = + useNotificationServiceContext(); + const { email } = notificationSettings; const { - updateCommunitySubscription, - updateAllEmailNotificationSubscription, - } = useNotificationServiceContext(); - + user: { addr }, + } = useWebContext(); + const { data: userCommunities } = useUserCommunities({ + addr, + count: 100, + initialLoading: false, + }); const handleSubscribeNotification = (signupAll) => { - updateCommunitySubscription( - communityId, - subscribeNotificationIntentions.subscribe - ); + const intentions = []; if (signupAll) { - updateAllEmailNotificationSubscription( - subscribeNotificationIntentions.subscribe - ); + userCommunities.forEach(({ id }) => { + intentions.push({ + communityId: id.toString(), + subscribeIntention: subscribeNotificationIntentions.subscribe, + }); + }); + } else { + intentions.push({ + communityId, + subscribeIntention: subscribeNotificationIntentions.subscribe, + }); } + updateCommunitySubscription(intentions); }; return ( @@ -25,6 +39,7 @@ const NotificationsModal = ({ onClose, communityId }) => { onSubscribe={handleSubscribeNotification} onClose={onClose} communityId={communityId} + userEmail={email} /> ); }; diff --git a/frontend/packages/client/src/contexts/NotificationService.js b/frontend/packages/client/src/contexts/NotificationService.js index 9137aec87..57cc7bcb8 100644 --- a/frontend/packages/client/src/contexts/NotificationService.js +++ b/frontend/packages/client/src/contexts/NotificationService.js @@ -4,10 +4,9 @@ import { getUserSettings as getUser, setUserEmail as setEmail, startLeanplumForUser, - subscribeCommunity, subscribeToEmailNotifications, - unsubscribeCommunity, unsubscribeFromEmailNotifications, + updateCommunitySubscription as updateCommunity, } from 'api/notificationService'; import { useWebContext } from './Web3'; @@ -19,23 +18,6 @@ const INIT_NOTIFICATION_SETTINGS = { isSubscribedFromCommunityUpdates: false, }; -const updateCommunitySubscriptionState = ( - communitySubscription, - communityId, - subscribedValue -) => { - const newCommunitySubscription = [...communitySubscription]; - const updateIndex = newCommunitySubscription.findIndex( - (communitySub) => communitySub.communityId === communityId - ); - if (updateIndex === -1) { - newCommunitySubscription.push({ communityId, subscribed: subscribedValue }); - } else { - newCommunitySubscription[updateIndex].subscribed = subscribedValue; - } - return newCommunitySubscription; -}; - export const useNotificationServiceContext = () => { const context = useContext(NotificationServiceContext); if (context === undefined) { @@ -86,6 +68,7 @@ const NotificationServiceProvider = ({ children }) => { try { const { communitySubscription, isSubscribedFromCommunityUpdates, email } = await getUser(addr); + console.log(communitySubscription); setNotificationSettings((prevState) => ({ ...prevState, communitySubscription, @@ -97,31 +80,12 @@ const NotificationServiceProvider = ({ children }) => { } }; - const updateCommunitySubscription = async ( - communityId, - subscribeIntention - ) => { + const updateCommunitySubscription = async (communitySubIntentions) => { try { - if (subscribeIntention === subscribeNotificationIntentions.subscribe) { - await subscribeCommunity(communityId); - } else if ( - subscribeIntention === subscribeNotificationIntentions.unsubscribe - ) { - await unsubscribeCommunity(communityId); - } - setNotificationSettings((prevState) => { - const newCommunitySubscription = updateCommunitySubscriptionState( - prevState.communitySubscription, - communityId, - subscribeIntention === subscribeNotificationIntentions.subscribe - ); - return { - ...prevState, - communitySubscription: newCommunitySubscription, - }; - }); + await updateCommunity(communitySubIntentions); + await getUserSettings(); } catch { - throw new Error('cannot subscribe community'); + throw new Error('cannot update community subscription'); } }; From 10781e39bd070af6d20c5cafcbb16ac33099e121 Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Mon, 31 Oct 2022 14:02:01 -0700 Subject: [PATCH 3/8] remove leanplum keys from env example --- frontend/packages/client/.env.example | 5 ----- .../src/components/Community/SubscribeCommunityButton.js | 2 +- frontend/packages/client/src/contexts/NotificationService.js | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/packages/client/.env.example b/frontend/packages/client/.env.example index a4ebfad74..731ea41c9 100644 --- a/frontend/packages/client/.env.example +++ b/frontend/packages/client/.env.example @@ -6,8 +6,3 @@ REACT_APP_IPFS_GATEWAY=https://dappercollectives.mypinata.cloud/ipfs REACT_APP_TX_OPTIONS_ADDRS="0xc590d541b72f0ac1,0x72d401812f579e3e" REACT_APP_HOTJAR_SITE_ID=0 REACT_APP_SENTRY_URL=REPLACE_SENTRY_URL -REACT_APP_LEANPLUM_APP_ID=app_DYMGKbyTTOtOfoQBJBJ6teLJI4usmMAlHX1CQ13TsuI -REACT_APP_LEANPLUM_DEV_KEY=dev_hMz1c4Irx8BQ6pBUZh5r3R5xJexNmCarsAXdSHrDvR8 -REACT_APP_LEANPLUM_PROD_KEY=prod_Ib9x94ei9fdFTCtpB34Wvueyq5fkbaJh4bbHMNihFvg -REACT_APP_LEANPLUM_EXPORT_KEY=exp_DhDP1lUDXBL52FSp3J0JGEEdGDMqsjZcGBzPDhMW9uY -REACT_APP_LEANPLUM_CRO_KEY=cro_3BW38MKRcTJwqWOcezlYKL5tKKQdmiy3HuMYIaukgIo \ No newline at end of file diff --git a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js index 74d2b00a6..13089caf4 100644 --- a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js +++ b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js @@ -19,7 +19,7 @@ export default function SubscribeCommunityButton({ useNotificationServiceContext(); const subscribedToCommunity = notificationSettings?.communitySubscription.some( - (c) => c.communityId === communityId && c.subscribed + (c) => c.communityId === communityId?.toString() && c.subscribed ); const subscribedToEmails = notificationSettings?.isSubscribedFromCommunityUpdates; diff --git a/frontend/packages/client/src/contexts/NotificationService.js b/frontend/packages/client/src/contexts/NotificationService.js index 57cc7bcb8..e9fc31f52 100644 --- a/frontend/packages/client/src/contexts/NotificationService.js +++ b/frontend/packages/client/src/contexts/NotificationService.js @@ -68,7 +68,6 @@ const NotificationServiceProvider = ({ children }) => { try { const { communitySubscription, isSubscribedFromCommunityUpdates, email } = await getUser(addr); - console.log(communitySubscription); setNotificationSettings((prevState) => ({ ...prevState, communitySubscription, From 80c11b4c697b8cb8140bdfca8f898f8840c0a7db Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Tue, 1 Nov 2022 15:03:10 -0700 Subject: [PATCH 4/8] full integration of the subscribe/unsubscribe flow --- .../Community/SubscribeCommunityButton.js | 115 ++++++++++++------ .../CommunitiesList.js | 25 +--- .../components/modals/Notifications/SignUp.js | 5 +- .../components/modals/Notifications/index.js | 5 +- 4 files changed, 86 insertions(+), 64 deletions(-) diff --git a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js index 13089caf4..3b06f5009 100644 --- a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js +++ b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js @@ -17,50 +17,95 @@ export default function SubscribeCommunityButton({ const { openModal, closeModal } = useModalContext(); const { notificationSettings, updateCommunitySubscription } = useNotificationServiceContext(); - const subscribedToCommunity = - notificationSettings?.communitySubscription.some( - (c) => c.communityId === communityId?.toString() && c.subscribed - ); - const subscribedToEmails = - notificationSettings?.isSubscribedFromCommunityUpdates; + const { communitySubscription, isSubscribedFromCommunityUpdates, email } = + notificationSettings; + + const subscribedToCommunity = communitySubscription.some( + (c) => c.communityId === communityId?.toString() && c.subscribed + ); + const subscribedToEmails = isSubscribedFromCommunityUpdates; const isSubscribed = subscribedToCommunity && subscribedToEmails; + const { popToast } = useToast(); const { user } = useWebContext(); const history = useHistory(); - - const onOpenModal = () => { - if (!user?.addr) { - openModal( - - } - onClose={closeModal} - />, - { isErrorModal: true } - ); - } else if (isSubscribed) { - updateCommunitySubscription( - communityId, - subscribeNotificationIntentions.unsubscribe - ); - const emailNotificationsState = subscribedToEmails ? 'on' : 'off'; + const openUpdateSubscriptionErorrModal = () => { + openModal( + + Close + + } + onClose={closeModal} + />, + { isErrorModal: true } + ); + }; + const openWalletErrorModal = () => { + openModal( + + } + onClose={closeModal} + />, + { isErrorModal: true } + ); + }; + const handleUpdateSubscription = async () => { + const subscribeIntention = isSubscribed + ? subscribeNotificationIntentions.unsubscribe + : subscribeNotificationIntentions.subscribe; + try { + await updateCommunitySubscription([ + { + communityId, + subscribeIntention, + }, + ]); + const emailNotificationsState = + subscribeIntention === subscribeNotificationIntentions.subscribe + ? 'on' + : 'off'; popToast({ message: `Email notifications are turned ${emailNotificationsState}`, - messageType: 'info', + messageType: 'success', actionFn: () => history.push('/settings'), actionText: 'Manage Settings', }); + } catch { + openUpdateSubscriptionErorrModal(); + } + }; + const handleSignUp = () => { + openModal( + , + { + classNameModalContent: 'rounded modal-content-sm', + showCloseButton: false, + } + ); + }; + const handleBellButtonClick = () => { + //if user is not connect to wallet open error modal + if (!user?.addr) { + openWalletErrorModal(); + return; + } + //if leanplum has user email handle the subscribe/unsubscribe and show toast + //if leanplumn doesn't have user email, show subscribe modal + if (email?.length > 0) { + handleUpdateSubscription(); } else { - openModal( - , - { - classNameModalContent: 'rounded modal-content-sm', - showCloseButton: false, - } - ); + handleSignUp(); } }; @@ -88,7 +133,7 @@ export default function SubscribeCommunityButton({ className={`column p-0 is-narrow-tablet is-full-mobile ${className}`} style={containerStyles} > - diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js index f10d9f08a..ae5ae1e24 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/CommunitiesList.js @@ -1,32 +1,11 @@ import Blockies from 'react-blockies'; -import { useWebContext } from 'contexts/Web3'; -import { useCommunityDetails, useUserCommunities } from 'hooks'; +import { useCommunityDetails } from 'hooks'; import { subscribeNotificationIntentions } from 'const'; export default function CommunitiesList({ communitySubscription, updateCommunitySubscription, }) { - const { - user: { addr }, - } = useWebContext(); - const { data: userCommunities, loading: isLoadingUserCommunities } = - useUserCommunities({ - addr, - count: 100, - initialLoading: false, - }); - const displayCommunities = isLoadingUserCommunities - ? [] - : (userCommunities || []).map(({ id }) => { - const isSubscribed = communitySubscription.find( - ({ communityId }) => communityId === id.toString() - )?.subscribed; - return { - communityId: id.toString(), - subscribed: !!isSubscribed, - }; - }); const handleUpdateCommunitySubscription = (communityId, subscribed) => { const subscribeIntention = subscribed ? subscribeNotificationIntentions.unsubscribe @@ -39,7 +18,7 @@ export default function CommunitiesList({ Edit which communities you want notifications from:
        - {displayCommunities.map(({ communityId, subscribed }) => ( + {communitySubscription.map(({ communityId, subscribed }) => ( { +const SignUpForm = ({ setErrorMessage, onSubscribe, onClose }) => { const [signupAll, setSignupAll] = useState(false); const { register, handleSubmit, formState } = useForm({ resolver: yupResolver(getSchema()), defaultValues: { - email: userEmail, + email: '', }, }); @@ -83,6 +83,7 @@ const SignUpForm = ({ setErrorMessage, onSubscribe, onClose, userEmail }) => { Close + } + onClose={closeModal} + />, + { isErrorModal: true } ); - notifyError(error); - throw error; + throw new Error(); } }; }; diff --git a/frontend/packages/client/src/components/modals/Notifications/SignUp.js b/frontend/packages/client/src/components/modals/Notifications/SignUp.js index 5adbd45b6..13465fcc9 100644 --- a/frontend/packages/client/src/components/modals/Notifications/SignUp.js +++ b/frontend/packages/client/src/components/modals/Notifications/SignUp.js @@ -19,7 +19,7 @@ const SignUpForm = ({ setErrorMessage, onSubscribe, onClose }) => { const onSubmit = async (formData) => { try { - onSubscribe(signupAll); + onSubscribe(formData.email, signupAll); onClose(); } catch (e) { setErrorMessage(e.message); diff --git a/frontend/packages/client/src/components/modals/Notifications/index.js b/frontend/packages/client/src/components/modals/Notifications/index.js index 901b2f9f7..6434ae181 100644 --- a/frontend/packages/client/src/components/modals/Notifications/index.js +++ b/frontend/packages/client/src/components/modals/Notifications/index.js @@ -4,8 +4,10 @@ import { useUserCommunities } from 'hooks'; import { subscribeNotificationIntentions } from 'const'; import NotificationsSignUp from './SignUp'; -const NotificationsModal = ({ onClose, communityId }) => { - const { updateCommunitySubscription } = useNotificationServiceContext(); +const NotificationsModal = ({ onClose, onError, onSuccess, communityId }) => { + const { updateCommunitySubscription, setUserEmail } = + useNotificationServiceContext(); + const { user: { addr }, } = useWebContext(); @@ -14,7 +16,7 @@ const NotificationsModal = ({ onClose, communityId }) => { count: 100, initialLoading: false, }); - const handleSubscribeNotification = (signupAll) => { + const handleSubscribeNotification = async (email, signupAll) => { const intentions = []; if (signupAll) { userCommunities.forEach(({ id }) => { @@ -29,9 +31,14 @@ const NotificationsModal = ({ onClose, communityId }) => { subscribeIntention: subscribeNotificationIntentions.subscribe, }); } - updateCommunitySubscription(intentions); + try { + await setUserEmail(email); + await updateCommunitySubscription(intentions); + onSuccess(subscribeNotificationIntentions.subscribe); + } catch { + onError(); + } }; - return ( { const updateCommunitySubscription = async (communitySubIntentions) => { try { await updateCommunity(communitySubIntentions); + await new Promise((r) => setTimeout(r, 500)); await getUserSettings(); } catch { throw new Error('cannot update community subscription'); } + // throw Error(); }; const updateAllEmailNotificationSubscription = async (subscribeIntention) => { From 4790c39108c0b16123582fdab6f88979a4399b51 Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Wed, 2 Nov 2022 09:28:38 -0700 Subject: [PATCH 6/8] add doc for function --- frontend/packages/client/src/api/notificationService.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/packages/client/src/api/notificationService.js b/frontend/packages/client/src/api/notificationService.js index 3bee957a6..aa9a05008 100644 --- a/frontend/packages/client/src/api/notificationService.js +++ b/frontend/packages/client/src/api/notificationService.js @@ -13,9 +13,10 @@ const options = { method: 'GET', headers: { accept: 'application/json' }, }; - +/* @param: communitySubIntentions : [{communityId:"1", subscribeIntention:"subscribe"},{communityId:"2",subscribeIntention:"unsubscribe"}] + * @return: {communityId1:'True', communityId2:'False'} + */ const getDesiredAttributes = (communitySubIntentions) => { - //subscribeUpdateIntentions = [{communityId:"1", subscribeIntention:"subscribe"},{communityId:"2",subscribeIntention:"unsubscribe"}] return communitySubIntentions .map(({ communityId, subscribeIntention }) => ({ key: `community${communityId}`, From c92c3afcf077bc32763e52905692b6665fb669ed Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Wed, 2 Nov 2022 13:42:01 -0700 Subject: [PATCH 7/8] created retry modal and moved error handling from component to context --- frontend/packages/client/src/App.js | 12 +- .../client/src/api/notificationService.js | 15 +- .../Community/SubscribeCommunityButton.js | 37 +---- .../NotificationSettingsSection/index.js | 41 +----- .../components/modals/Notifications/index.js | 12 +- .../client/src/components/modals/Retry.js | 50 +++++++ .../client/src/components/modals/index.js | 1 + .../src/contexts/NotificationService.js | 134 ++++++++++++------ 8 files changed, 169 insertions(+), 133 deletions(-) create mode 100644 frontend/packages/client/src/components/modals/Retry.js diff --git a/frontend/packages/client/src/App.js b/frontend/packages/client/src/App.js index 099e2b062..5a77f2a7d 100644 --- a/frontend/packages/client/src/App.js +++ b/frontend/packages/client/src/App.js @@ -35,13 +35,13 @@ function App() { {/* using resetCSS to avoid conficts */} - - - + + + - - - + + + diff --git a/frontend/packages/client/src/api/notificationService.js b/frontend/packages/client/src/api/notificationService.js index aa9a05008..186f07fa5 100644 --- a/frontend/packages/client/src/api/notificationService.js +++ b/frontend/packages/client/src/api/notificationService.js @@ -65,13 +65,11 @@ export const getUserSettings = async (walletId) => { if (!data.userAttributes) { throw new Error('User Not Found'); } - const communitySubscriptions = []; const res = { email: data.userAttributes.email, }; let isSubscribedFromCommunityUpdates = true; - for (const property in data.userAttributes) { if ( property.includes('community') && @@ -84,9 +82,7 @@ export const getUserSettings = async (walletId) => { }); } } - res.communitySubscription = communitySubscriptions; - if (data.unsubscribeCategories) { data.unsubscribeCategories.forEach((category) => { if (parseInt(category.id) === COMMUNITY_UPDATES_CATEGORY_ID) { @@ -94,21 +90,18 @@ export const getUserSettings = async (walletId) => { } }); } - res.isSubscribedFromCommunityUpdates = isSubscribedFromCommunityUpdates; return res; } catch (e) { throw new Error(e); } + // setTimeout(() => { + // throw new Error('get user setting error'); + // }, 500); }; export const setUserEmail = async (email) => { - try { - await Leanplum.setUserAttributes({ email }); - return true; - } catch (e) { - throw new Error(e); - } + Leanplum.setUserAttributes({ email }); }; export const updateCommunitySubscription = async (communitySubIntentions) => { diff --git a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js index 2e64c1a32..326eddfba 100644 --- a/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js +++ b/frontend/packages/client/src/components/Community/SubscribeCommunityButton.js @@ -29,24 +29,6 @@ export default function SubscribeCommunityButton({ const { popToast } = useToast(); const { user } = useWebContext(); const history = useHistory(); - const openUpdateSubscriptionErorrModal = () => { - openModal( - - Close - - } - onClose={closeModal} - />, - { isErrorModal: true } - ); - }; const openWalletErrorModal = () => { openModal( { openModal( , { diff --git a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js index 8a392a71d..6aab651ba 100644 --- a/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js +++ b/frontend/packages/client/src/components/Settings/NotificationSettingsSection/index.js @@ -1,6 +1,4 @@ -import { useModalContext } from 'contexts/NotificationModal'; import { useNotificationServiceContext } from 'contexts/NotificationService'; -import { ErrorModal } from 'components'; import CommunitiesList from './CommunitiesList'; import EmailAddressInput from './EmailAddressInput'; import ReceiveEmailNotificationsSwitch from './ReceiveEmailNotificationsSwitch'; @@ -14,32 +12,6 @@ export default function NotificationSettingsSection() { } = useNotificationServiceContext(); const { communitySubscription, email, isSubscribedFromCommunityUpdates } = notificationSettings; - const { openModal, closeModal } = useModalContext(); - const handleError = (fn) => { - return async (...args) => { - try { - await fn(...args); - } catch { - openModal( - - Close - - } - onClose={closeModal} - />, - { isErrorModal: true } - ); - throw new Error(); - } - }; - }; return (

        Notification Settings

        @@ -54,24 +26,19 @@ export default function NotificationSettingsSection() { )} {communitySubscription.length > 0 && ( <> - +

        {isSubscribedFromCommunityUpdates && ( )}

        diff --git a/frontend/packages/client/src/components/modals/Notifications/index.js b/frontend/packages/client/src/components/modals/Notifications/index.js index 6434ae181..5490f8be5 100644 --- a/frontend/packages/client/src/components/modals/Notifications/index.js +++ b/frontend/packages/client/src/components/modals/Notifications/index.js @@ -4,7 +4,7 @@ import { useUserCommunities } from 'hooks'; import { subscribeNotificationIntentions } from 'const'; import NotificationsSignUp from './SignUp'; -const NotificationsModal = ({ onClose, onError, onSuccess, communityId }) => { +const NotificationsModal = ({ onClose, onSuccess, communityId }) => { const { updateCommunitySubscription, setUserEmail } = useNotificationServiceContext(); @@ -31,13 +31,9 @@ const NotificationsModal = ({ onClose, onError, onSuccess, communityId }) => { subscribeIntention: subscribeNotificationIntentions.subscribe, }); } - try { - await setUserEmail(email); - await updateCommunitySubscription(intentions); - onSuccess(subscribeNotificationIntentions.subscribe); - } catch { - onError(); - } + await setUserEmail(email); + await updateCommunitySubscription(intentions); + onSuccess(subscribeNotificationIntentions.subscribe); }; return ( {}, + onError = () => {}, +}) { + const [isRetrying, setIsRetrying] = useState(false); + const displayMessage = isRetrying ? 'trying...' : message; + const handleRetry = (retryFn) => { + return async (...arg) => { + setIsRetrying(true); + try { + const result = await retryFn(...arg); + onSuccess(result); + } catch (e) { + onError(e); + } finally { + setTimeout(() => setIsRetrying(false), 3000); + } + }; + }; + return ( + + + + + } + onClose={closeModal} + /> + ); +} diff --git a/frontend/packages/client/src/components/modals/index.js b/frontend/packages/client/src/components/modals/index.js index 1016351c0..e035af262 100644 --- a/frontend/packages/client/src/components/modals/index.js +++ b/frontend/packages/client/src/components/modals/index.js @@ -4,3 +4,4 @@ export { default as VoteConfirmation } from './VoteConfirmation'; export { default as CastingVote } from './CastingVote'; export { default as VoteConfirmed } from './VoteConfirmed'; export { default as CancelProposal } from './CancelProposal'; +export { default as Retry } from './Retry'; diff --git a/frontend/packages/client/src/contexts/NotificationService.js b/frontend/packages/client/src/contexts/NotificationService.js index b5bb3d7d1..f50d97a61 100644 --- a/frontend/packages/client/src/contexts/NotificationService.js +++ b/frontend/packages/client/src/contexts/NotificationService.js @@ -1,4 +1,6 @@ import { createContext, useContext, useEffect, useState } from 'react'; +import { ErrorModal } from 'components'; +import { Retry } from 'components/modals'; import { subscribeNotificationIntentions } from 'const'; import { getUserSettings as getUser, @@ -8,6 +10,8 @@ import { unsubscribeFromEmailNotifications, updateCommunitySubscription as updateCommunity, } from 'api/notificationService'; +import { debounce } from 'lodash'; +import { useModalContext } from './NotificationModal'; import { useWebContext } from './Web3'; const NotificationServiceContext = createContext({}); @@ -36,34 +40,68 @@ const NotificationServiceProvider = ({ children }) => { const { user: { addr }, } = useWebContext(); + const { openModal, closeModal } = useModalContext(); useEffect(() => { if (addr) { - (async () => { - try { - await startLeanplumForUser(addr); - await getUserSettings(); - } catch (e) { - console.log(e); - } - })(); + initUser(); } else { setNotificationSettings(INIT_NOTIFICATION_SETTINGS); } }, [addr]); - const setUserEmail = async (email) => { - try { - await setEmail(email); - setNotificationSettings((prevState) => ({ - ...prevState, - email, - })); - } catch { - throw new Error('cannot set user email'); - } - }; + const initUser = debounce( + async () => { + try { + console.log('init user called'); + await startLeanplumForUser(addr); + await getUserSettings(); + } catch (e) { + openRetryModal(); + } + }, + //Leanplum API rate limit is 1QPS + 2000, + { leading: true, trailing: false } + ); + const openRetryModal = () => { + openModal( + , + { + isErrorModal: true, + } + ); + }; + const handleNotificationServiceError = (fn) => { + return async (...args) => { + try { + await fn(...args); + } catch { + openModal( + + Close + + } + onClose={closeModal} + />, + { isErrorModal: true } + ); + throw new Error(); + } + }; + }; const getUserSettings = async () => { try { const { communitySubscription, isSubscribedFromCommunityUpdates, email } = @@ -78,37 +116,51 @@ const NotificationServiceProvider = ({ children }) => { throw new Error('cannot get user settings'); } }; - - const updateCommunitySubscription = async (communitySubIntentions) => { + const setUserEmail = handleNotificationServiceError(async (email) => { try { - await updateCommunity(communitySubIntentions); - await new Promise((r) => setTimeout(r, 500)); - await getUserSettings(); + await setEmail(email); + setNotificationSettings((prevState) => ({ + ...prevState, + email, + })); } catch { - throw new Error('cannot update community subscription'); + throw new Error('cannot set user email'); } - // throw Error(); - }; + }); - const updateAllEmailNotificationSubscription = async (subscribeIntention) => { - if (subscribeIntention === subscribeNotificationIntentions.resubscribe) { - subscribeToEmailNotifications(addr); - } else if ( - subscribeIntention === subscribeNotificationIntentions.unsubscribe - ) { - unsubscribeFromEmailNotifications(addr); + const updateCommunitySubscription = handleNotificationServiceError( + async (communitySubIntentions) => { + try { + await updateCommunity(communitySubIntentions); + await new Promise((r) => setTimeout(r, 500)); + await getUserSettings(); + } catch { + throw new Error('cannot update community subscription'); + } + // throw Error(); } - setNotificationSettings((prevState) => ({ - ...prevState, - isSubscribedFromCommunityUpdates: - subscribeIntention === subscribeNotificationIntentions.resubscribe, - })); - }; + ); + + const updateAllEmailNotificationSubscription = handleNotificationServiceError( + async (subscribeIntention) => { + if (subscribeIntention === subscribeNotificationIntentions.resubscribe) { + subscribeToEmailNotifications(addr); + } else if ( + subscribeIntention === subscribeNotificationIntentions.unsubscribe + ) { + unsubscribeFromEmailNotifications(addr); + } + setNotificationSettings((prevState) => ({ + ...prevState, + isSubscribedFromCommunityUpdates: + subscribeIntention === subscribeNotificationIntentions.resubscribe, + })); + } + ); const providerProps = { notificationSettings, setUserEmail, - getUserSettings, updateCommunitySubscription, updateAllEmailNotificationSubscription, }; From 41e4bd102184361a3882fb0b97472b04d585b444 Mon Sep 17 00:00:00 2001 From: Linxiao Li Date: Wed, 2 Nov 2022 14:49:14 -0700 Subject: [PATCH 8/8] organize retry modal import --- frontend/packages/client/src/components/index.js | 1 + frontend/packages/client/src/contexts/NotificationService.js | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/packages/client/src/components/index.js b/frontend/packages/client/src/components/index.js index 7ac6a63dd..38c57885d 100644 --- a/frontend/packages/client/src/components/index.js +++ b/frontend/packages/client/src/components/index.js @@ -50,6 +50,7 @@ export { default as TooltipMessage } from './TooltipMessage'; export { default as FadeInOut } from './FadeInOut'; export { Error as ErrorModal, + Retry as RetryModal, VoteConfirmation as VoteConfirmationModal, CastingVote as CastingVoteModal, VoteConfirmed as VoteConfirmedModal, diff --git a/frontend/packages/client/src/contexts/NotificationService.js b/frontend/packages/client/src/contexts/NotificationService.js index f50d97a61..e283c9f43 100644 --- a/frontend/packages/client/src/contexts/NotificationService.js +++ b/frontend/packages/client/src/contexts/NotificationService.js @@ -1,6 +1,5 @@ import { createContext, useContext, useEffect, useState } from 'react'; -import { ErrorModal } from 'components'; -import { Retry } from 'components/modals'; +import { ErrorModal, RetryModal } from 'components'; import { subscribeNotificationIntentions } from 'const'; import { getUserSettings as getUser, @@ -67,7 +66,7 @@ const NotificationServiceProvider = ({ children }) => { const openRetryModal = () => { openModal( -