From 8c2628c9c54541b83618889d4be12ddf701cfacc Mon Sep 17 00:00:00 2001 From: abhishek-01k Date: Fri, 23 Aug 2024 05:03:46 +0530 Subject: [PATCH] Added new UI for notification settings page --- src/blocks/icons/components/Pencil.tsx | 40 +++ src/blocks/icons/index.ts | 2 + src/blocks/textInput/TextInput.tsx | 6 +- src/common/Common.types.ts | 5 + src/common/hooks/index.ts | 1 + src/common/hooks/useModal.ts | 17 ++ src/common/index.ts | 1 + .../ChannelDashboard.types.ts | 12 +- .../components/ChannelDashboardNullState.tsx | 51 ++-- .../EditNotificationSetting.form.tsx | 51 ++++ .../NotificationSettings.constants.ts | 6 + .../notifSettings/NotificationSettings.tsx | 59 +++++ .../NotificationSettings.types.ts | 35 +++ .../AddNotificationSettingsModalContent.tsx | 131 ++++++++++ .../components/NotificationSettingsBody.tsx | 93 +++++++ .../components/NotificationSettingsFooter.tsx | 226 +++++++++++++++++ .../components/NotificationSettingsHeader.tsx | 53 ++++ .../NotificationSettingsListItem.tsx | 85 +++++++ .../components/NotificationSettingsLists.tsx | 53 ++++ .../components/NotificationSettingsModal.tsx | 84 +++++++ .../NotificationSettingsRangeSelector.tsx | 227 ++++++++++++++++++ src/pages/NotificationSettingsPage.tsx | 12 + src/queries/hooks/index.ts | 1 + .../hooks/notificationSettings/index.ts | 1 + .../useCreateNotificationSettings.ts | 9 + src/queries/queryKeys.ts | 1 + src/queries/services/index.ts | 1 + .../addNotificationSettings.ts | 8 + .../services/notificationSettings/index.ts | 1 + src/queries/types/index.ts | 1 + src/queries/types/notificationsettings.ts | 6 + src/structure/MasterInterfacePage.tsx | 7 +- 32 files changed, 1261 insertions(+), 25 deletions(-) create mode 100644 src/blocks/icons/components/Pencil.tsx create mode 100644 src/common/Common.types.ts create mode 100644 src/common/hooks/useModal.ts create mode 100644 src/modules/notifSettings/EditNotificationSetting.form.tsx create mode 100644 src/modules/notifSettings/NotificationSettings.constants.ts create mode 100644 src/modules/notifSettings/NotificationSettings.tsx create mode 100644 src/modules/notifSettings/NotificationSettings.types.ts create mode 100644 src/modules/notifSettings/components/AddNotificationSettingsModalContent.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsBody.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsFooter.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsHeader.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsListItem.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsLists.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsModal.tsx create mode 100644 src/modules/notifSettings/components/NotificationSettingsRangeSelector.tsx create mode 100644 src/pages/NotificationSettingsPage.tsx create mode 100644 src/queries/hooks/notificationSettings/index.ts create mode 100644 src/queries/hooks/notificationSettings/useCreateNotificationSettings.ts create mode 100644 src/queries/services/notificationSettings/addNotificationSettings.ts create mode 100644 src/queries/services/notificationSettings/index.ts create mode 100644 src/queries/types/notificationsettings.ts diff --git a/src/blocks/icons/components/Pencil.tsx b/src/blocks/icons/components/Pencil.tsx new file mode 100644 index 0000000000..a7b5235380 --- /dev/null +++ b/src/blocks/icons/components/Pencil.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react'; +import { IconWrapper } from '../IconWrapper'; +import { IconProps } from '../Icons.types'; + +const Pencil: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + } + {...restProps} + /> + ); +}; + +export default Pencil; diff --git a/src/blocks/icons/index.ts b/src/blocks/icons/index.ts index dfc0a4bb33..eaa4e47ac8 100644 --- a/src/blocks/icons/index.ts +++ b/src/blocks/icons/index.ts @@ -98,6 +98,8 @@ export { default as NotificationMobile } from './components/NotificationMobile'; export { default as OptOut } from './components/OptOut'; +export { default as Pencil } from './components/Pencil'; + export { default as PlusCircle } from './components/PlusCircle'; export { default as PlusCircleFilled } from './components/PlusCircleFilled'; diff --git a/src/blocks/textInput/TextInput.tsx b/src/blocks/textInput/TextInput.tsx index 5d2502a1f5..44dadc24d8 100644 --- a/src/blocks/textInput/TextInput.tsx +++ b/src/blocks/textInput/TextInput.tsx @@ -9,7 +9,7 @@ export type TextInputProps = { disabled?: boolean; icon?: ReactNode; error?: boolean; - type?: 'text' | 'password'; + type?: 'text' | 'password' | 'number'; errorMessage?: string; label?: string; onChange?: (e: React.ChangeEvent) => void; @@ -18,7 +18,7 @@ export type TextInputProps = { required?: boolean; success?: boolean; totalCount?: number; - value: string; + value: string | number; }; const Container = styled.div<{ css?: FlattenSimpleInterpolation }>` @@ -168,7 +168,7 @@ export const TextInput = forwardRef( color={disabled ? 'components-inputs-text-disabled' : 'components-inputs-text-secondary'} variant="c-regular" > - {`${value?.length || 0} / ${totalCount}`} + {`${typeof value === 'string' && value?.length || 0} / ${totalCount}`} )} diff --git a/src/common/Common.types.ts b/src/common/Common.types.ts new file mode 100644 index 0000000000..931d784879 --- /dev/null +++ b/src/common/Common.types.ts @@ -0,0 +1,5 @@ +export type ModalResponse = { + isOpen: boolean; + onClose: () => void; + open: () => void; +}; diff --git a/src/common/hooks/index.ts b/src/common/hooks/index.ts index 871bdf7d48..5a4af72059 100644 --- a/src/common/hooks/index.ts +++ b/src/common/hooks/index.ts @@ -1,2 +1,3 @@ export { useIsVisible } from './useIsVisible'; export { usePushStakingStats } from './usePushStakingStats'; +export { useModal } from './useModal'; diff --git a/src/common/hooks/useModal.ts b/src/common/hooks/useModal.ts new file mode 100644 index 0000000000..40241e82ff --- /dev/null +++ b/src/common/hooks/useModal.ts @@ -0,0 +1,17 @@ +import { ModalResponse } from 'common/common.types'; +import { useState, useCallback } from 'react'; + +const useModal = (): ModalResponse => { + const [isOpen, setIsOpen] = useState(false); + + const open = useCallback(() => setIsOpen(true), []); + const onClose = useCallback(() => setIsOpen(false), []); + + return { + isOpen, + onClose, + open, + }; +}; + +export { useModal }; diff --git a/src/common/index.ts b/src/common/index.ts index 43949e8236..9cf1a3126b 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -3,3 +3,4 @@ export * from './components'; export * from './Common.constants'; export * from './Common.utils'; export * from './Common.form'; +export * from './common.types'; diff --git a/src/modules/channelDashboard/ChannelDashboard.types.ts b/src/modules/channelDashboard/ChannelDashboard.types.ts index 67efdee8f4..38cbf5de9d 100644 --- a/src/modules/channelDashboard/ChannelDashboard.types.ts +++ b/src/modules/channelDashboard/ChannelDashboard.types.ts @@ -1,5 +1,5 @@ export type ChannelSetting = { - type: 1 | 2 | 3; + type: number; default: | boolean | number @@ -7,12 +7,12 @@ export type ChannelSetting = { lower: number; upper: number; }; - enabled: boolean; + enabled?: boolean; description: string; - index: number; - lowerLimit: number; - upperLimit: number; - ticker: number; + index?: number; + lowerLimit?: number; + upperLimit?: number; + ticker?: number; }; export type DashboardActiveState = diff --git a/src/modules/channelDashboard/components/ChannelDashboardNullState.tsx b/src/modules/channelDashboard/components/ChannelDashboardNullState.tsx index 75072a9193..e059c43667 100644 --- a/src/modules/channelDashboard/components/ChannelDashboardNullState.tsx +++ b/src/modules/channelDashboard/components/ChannelDashboardNullState.tsx @@ -1,6 +1,6 @@ -import { Box, Button, CrownSimple, ReceiveNotification, Text } from "blocks"; -import { FC } from "react"; +import { FC } from 'react'; +import { Box, Button, CrownSimple, ReceiveNotification, Text } from 'blocks'; type ChannelDashboardNullStateProps = { state: 'notificationSettings' | 'delegatee'; @@ -9,12 +9,7 @@ type ChannelDashboardNullStateProps = { onClick?: any; }; -const ChannelDashboardNullState: FC = ({ - state, - title, - subTitle, - onClick -}) => { +const ChannelDashboardNullState: FC = ({ state, title, subTitle, onClick }) => { return ( = ({ gap="spacing-sm" height="200px" > - {state == 'delegatee' && } - {state == 'notificationSettings' && } + {state == 'delegatee' && ( + + )} + {state == 'notificationSettings' && ( + + )} - - + + {title} - + {subTitle} {onClick && ( - )} @@ -46,4 +67,4 @@ const ChannelDashboardNullState: FC = ({ ); }; -export { ChannelDashboardNullState }; \ No newline at end of file +export { ChannelDashboardNullState }; diff --git a/src/modules/notifSettings/EditNotificationSetting.form.tsx b/src/modules/notifSettings/EditNotificationSetting.form.tsx new file mode 100644 index 0000000000..dd5f2eabf4 --- /dev/null +++ b/src/modules/notifSettings/EditNotificationSetting.form.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react'; + +import { FormikProvider, useFormik, useFormikContext } from 'formik'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +type EditNotificationSettingsFormProviderProps = { + children: React.ReactNode; + initialValue: ChannelSetting; + onSubmit: (values: NotificationSettingFormValues) => void; +}; + +export const getFormInitialValus = (initialValue: ChannelSetting): NotificationSettingFormValues => { + return { + settingName: initialValue.description, + isDefault: + initialValue.type === 1 ? (typeof initialValue.default === 'boolean' ? true : false) : initialValue.enabled!, + enableRange: initialValue.type !== 1 ? true : false, + rangelowerlimit: initialValue.lowerLimit ? initialValue.lowerLimit : 0, + rangeupperlimit: initialValue.upperLimit ? initialValue.upperLimit : 0, + enableMultiRange: initialValue.type === 3 ? true : false, + defaultValue: typeof initialValue.default === 'number' ? initialValue.default : 0, + multirangelowerlimit: typeof initialValue.default === 'object' ? initialValue.default.lower : 0, + multirangeupperlimit: typeof initialValue.default === 'object' ? initialValue.default.upper : 0, + sliderStepValue: initialValue.ticker ? initialValue.ticker : 0, + }; +}; + +const EditNotificationSettingsFormProvider: FC = ({ + children, + initialValue, + onSubmit, +}) => { + const addNotificationSettingsForm = useFormik({ + initialValues: getFormInitialValus(initialValue), + onSubmit: onSubmit, + }); + + return {children}; +}; + +const useEditNotificationSettingsForm = () => { + const context = useFormikContext(); + + if (!context) { + throw new Error('useEditNotificationSettingsForm must be used within a EditNotificationSettingsFormProvider'); + } + return context; +}; + +export { useEditNotificationSettingsForm, EditNotificationSettingsFormProvider }; diff --git a/src/modules/notifSettings/NotificationSettings.constants.ts b/src/modules/notifSettings/NotificationSettings.constants.ts new file mode 100644 index 0000000000..a2658356fd --- /dev/null +++ b/src/modules/notifSettings/NotificationSettings.constants.ts @@ -0,0 +1,6 @@ +export const settingInitialValue = { + type: 1, + default: 0, + description: '', + index: 0, +}; diff --git a/src/modules/notifSettings/NotificationSettings.tsx b/src/modules/notifSettings/NotificationSettings.tsx new file mode 100644 index 0000000000..f3e7032166 --- /dev/null +++ b/src/modules/notifSettings/NotificationSettings.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; + +import { Box } from 'blocks'; + +import { useModal } from 'common'; + +import { useAccount } from 'hooks'; + +import { useGetChannelDetails } from 'queries'; + +import { NotificationSettingsHeader } from './components/NotificationSettingsHeader'; +import { NotificationSettingsBody } from './components/NotificationSettingsBody'; +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; +import { settingInitialValue } from './NotificationSettings.constants'; + +const NotificationSettings = () => { + const { account } = useAccount(); + + const { data: channelDetails, isLoading: loadingChannelDetails } = useGetChannelDetails(account); + const channelSettings = channelDetails?.channel_settings ? channelDetails?.channel_settings : ''; + + const modifiedChannelSettings = loadingChannelDetails + ? Array(3).fill(0) + : channelSettings + ? JSON.parse(channelSettings) + : []; + + const modalControl = useModal(); + + const [settingsToEdit, setSettingsToEdit] = useState(settingInitialValue); + + return ( + + + + + + ); +}; + +export { NotificationSettings }; diff --git a/src/modules/notifSettings/NotificationSettings.types.ts b/src/modules/notifSettings/NotificationSettings.types.ts new file mode 100644 index 0000000000..2390c4651c --- /dev/null +++ b/src/modules/notifSettings/NotificationSettings.types.ts @@ -0,0 +1,35 @@ +type NotificationSettingFormValues = { + settingName: string; + isDefault: boolean; + enableRange: boolean; + rangelowerlimit: number; + rangeupperlimit: number; + enableMultiRange: boolean; + defaultValue: number; + multirangelowerlimit: number; + multirangeupperlimit: number; + sliderStepValue: number; +}; + +type NotificationSettings = { + type: number; + default: + | boolean + | number + | { + lower: number; + upper: number; + }; + enabled?: boolean; + description: string; + index?: number; + lowerLimit?: number; + upperLimit?: number; + ticker?: number; +}; + +type NotificationSettingModal = { + isOpen: boolean; + onClose: () => void; + open: () => void; +}; diff --git a/src/modules/notifSettings/components/AddNotificationSettingsModalContent.tsx b/src/modules/notifSettings/components/AddNotificationSettingsModalContent.tsx new file mode 100644 index 0000000000..929490fc62 --- /dev/null +++ b/src/modules/notifSettings/components/AddNotificationSettingsModalContent.tsx @@ -0,0 +1,131 @@ +import { FC } from 'react'; + +import { Box, Button, Text, TextInput, ToggleSwitch } from 'blocks'; + +import { ModalResponse } from 'common'; + +import { NotificationSettingsRangeSelector } from './NotificationSettingsRangeSelector'; +import { useEditNotificationSettingsForm } from '../EditNotificationSetting.form'; + +type AddNotificationSettingsModalContentProps = { + modalControl: ModalResponse; +}; + +const AddNotificationSettingsModalContent: FC = ({ modalControl }) => { + const { + values: formValues, + handleSubmit, + handleChange, + touched, + errors, + setFieldValue, + } = useEditNotificationSettingsForm(); + + const { onClose } = modalControl; + return ( + +
+ + + Add a Setting + + + + + + + + Set as Default + + Setting on for users by default + + setFieldValue('isDefault', checked)} + /> + + + + + + Range + + Set a range for this setting e.g. 1-10 + + setFieldValue('enableRange', checked)} + /> + + {formValues.enableRange && } + + + + + + + +
+
+ ); +}; + +export { AddNotificationSettingsModalContent }; diff --git a/src/modules/notifSettings/components/NotificationSettingsBody.tsx b/src/modules/notifSettings/components/NotificationSettingsBody.tsx new file mode 100644 index 0000000000..d694358e36 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsBody.tsx @@ -0,0 +1,93 @@ +import { FC, useEffect, useState } from 'react'; + +import { Box } from 'blocks'; + +import { ModalResponse } from 'common'; + +import { ChannelDashboardNullState } from 'modules/channelDashboard/components/ChannelDashboardNullState'; +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +import { NotificationSettingsLists } from './NotificationSettingsLists'; +import { NotificationSettingsFooter } from './NotificationSettingsFooter'; +import { NotificationSettingsModal } from './NotificationSettingsModal'; + +type NotificationSettingsBodyProps = { + modalControl: ModalResponse; + channelSettings: ChannelSetting[]; + loadingSettings: boolean; + settingsToEdit: ChannelSetting; + setSettingsToEdit: (settingsToEdit: ChannelSetting) => void; +}; + +const NotificationSettingsBody: FC = ({ + modalControl, + channelSettings, + loadingSettings, + settingsToEdit, + setSettingsToEdit, +}) => { + const [newSettings, setNewSettings] = useState([]); + + const { open } = modalControl; + + useEffect(() => { + if (channelSettings && !loadingSettings) { + setNewSettings(channelSettings); + } + }, [loadingSettings]); + + const handleDeleteSetting = (settingToDelete: ChannelSetting) => { + setNewSettings((settings) => settings.filter((setting) => setting.index !== settingToDelete.index)); + }; + + const handleSettingsChange = (newSetting: ChannelSetting) => { + const index = newSettings.findIndex((setting) => setting.index === newSetting.index); + if (index === -1) setNewSettings([...newSettings, newSetting]); + else { + const updatedSetting = [...newSettings]; + updatedSetting[index] = newSetting; + setNewSettings(updatedSetting); + } + }; + + return ( + + {newSettings.length > 0 ? ( + + ) : ( + + )} + + + + + + ); +}; + +export { NotificationSettingsBody }; diff --git a/src/modules/notifSettings/components/NotificationSettingsFooter.tsx b/src/modules/notifSettings/components/NotificationSettingsFooter.tsx new file mode 100644 index 0000000000..4ba0e527b1 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsFooter.tsx @@ -0,0 +1,226 @@ +import { FC, useEffect, useMemo, useState } from 'react'; + +import { ethers } from 'ethers'; +import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; + +import { Alert, Box, Button } from 'blocks'; + +import { StakingVariant } from 'common'; +import { addresses } from 'config'; +import APP_PATHS from 'config/AppPaths'; +import { useAppContext } from 'contexts/AppContext'; + +import { getPushTokenApprovalAmount } from 'helpers'; +import { useAccount } from 'hooks'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; +import { useApprovePUSHToken, useCreateNotificationSettings } from 'queries'; +import useFetchChannelDetails from 'common/hooks/useFetchUsersChannelDetails'; + +type NotificationSettingsFooterProps = { + newSettings: ChannelSetting[]; + channelSettings: ChannelSetting[]; +}; + +const EDIT_SETTING_FEE = 50; + +const NotificationSettingsFooter: FC = ({ newSettings, channelSettings }) => { + const navigate = useNavigate(); + const { account, provider, wallet } = useAccount(); + + const { userPushSDKInstance } = useSelector((state: any) => { + return state.user; + }); + + const { handleConnectWalletAndEnableProfile } = useAppContext(); + const { refetchChannelDetails } = useFetchChannelDetails(); + + const [pushApprovalAmount, setPushApprovalAmount] = useState(0); + const [errorMessage, setErrorMessage] = useState(''); + + const checkApprovedPUSHTokenAmount = async () => { + const pushTokenApprovalAmount = await getPushTokenApprovalAmount({ + address: account, + provider: provider, + contractAddress: addresses.epnscore, + }); + setPushApprovalAmount(parseInt(pushTokenApprovalAmount)); + }; + + useEffect(() => { + if (!account || !provider) return; + checkApprovedPUSHTokenAmount(); + }, [account, provider]); + + const { mutate: approvePUSHToken, isPending: approvingPUSH } = useApprovePUSHToken(); + + const approvePUSH = async () => { + if (!provider) return; + const signer = provider.getSigner(account); + + const fees = ethers.utils.parseUnits((EDIT_SETTING_FEE - pushApprovalAmount).toString(), 18); + + approvePUSHToken( + { + noOfTokenToApprove: fees, + signer, + }, + { + onSuccess: () => { + checkApprovedPUSHTokenAmount(); + }, + onError: (error: any) => { + console.log('Error in Approving PUSH', error); + + if (error.code == 'ACTION_REJECTED') { + setErrorMessage('User rejected signature. Please try again.'); + } else { + setErrorMessage('Error in approving PUSH Tokens'); + } + }, + } + ); + }; + + const { mutate: createNotificationSettings, isPending: addingNotificationSettings } = useCreateNotificationSettings(); + + const handleSaveSettings = async () => { + let userPushInstance = userPushSDKInstance; + if (!userPushInstance.signer) { + userPushInstance = await handleConnectWalletAndEnableProfile({ wallet }); + console.log('User Push Instance >>>', userPushInstance); + if (!userPushInstance) { + return; + } + } + + if (newSettings.length > 0) { + const newsettingData: ChannelSetting[] = newSettings.map((setting) => { + if (setting.type === 1) { + return { + type: setting.type, + description: setting.description, + default: setting.default ? 1 : 0, + }; + } else { + return { + type: setting.type, + description: setting.description, + default: setting.default, + data: { + lower: setting.lowerLimit, + upper: setting.upperLimit, + ticker: setting.ticker, + enabled: setting.enabled, + }, + }; + } + }); + + createNotificationSettings( + { + userPushSDKInstance: userPushInstance, + settings: newsettingData, + }, + { + onSuccess: (response) => { + console.log('onSuccess >>>', response); + if (response.transactionHash) { + console.log('Call channel details refetch and navigate to dashboard page'); + refetchChannelDetails(); + } + }, + onError: (error: any) => { + console.log('Error in adding setting', error); + setErrorMessage(error.message); + }, + } + ); + } + }; + + const settingsChanged = useMemo(() => { + if (!channelSettings) return false; // if there are no channel settings + if (newSettings.length !== channelSettings.length) return true; // new settings length is not equal to channel settings + let isUnchanged = true; + for (let i = 0; i < newSettings.length; i++) { + const setting1 = newSettings[i]; + const setting2 = channelSettings[i]; + if (setting1.type === 1) { + isUnchanged = + isUnchanged && + setting1.type === setting2.type && + setting1.description === setting2.description && + setting1.default === setting2.default; + } else if (setting1.type === 2) { + isUnchanged = + isUnchanged && + setting1.type === setting2.type && + setting1.description === setting2.description && + setting1.default === setting2.default && + setting1.enabled === setting2.enabled && + setting1.lowerLimit === setting2.lowerLimit && + setting1.upperLimit === setting2.upperLimit && + setting1.ticker === setting2.ticker; + } + } + return isUnchanged === false; + }, [newSettings, channelSettings]); + + return ( + + {errorMessage && ( + + )} + + + + + + + {pushApprovalAmount >= EDIT_SETTING_FEE ? ( + + ) : ( + + )} + + + ); +}; + +export { NotificationSettingsFooter }; diff --git a/src/modules/notifSettings/components/NotificationSettingsHeader.tsx b/src/modules/notifSettings/components/NotificationSettingsHeader.tsx new file mode 100644 index 0000000000..09ef370cd1 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsHeader.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react'; + +import { Add, Box, Button, Text } from 'blocks'; + +import { ModalResponse } from 'common'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; +import { settingInitialValue } from '../NotificationSettings.constants'; + +type NotificationSettingsHeaderProps = { + modalControl: ModalResponse; + setSettingsToEdit: (settingsToEdit: ChannelSetting) => void; +}; +const NotificationSettingsHeader: FC = ({ modalControl, setSettingsToEdit }) => { + const { open } = modalControl; + + return ( + + + + Notification Settings + + + Add, Edit or Remove Notification Settings + + + + + ); +}; + +export { NotificationSettingsHeader }; diff --git a/src/modules/notifSettings/components/NotificationSettingsListItem.tsx b/src/modules/notifSettings/components/NotificationSettingsListItem.tsx new file mode 100644 index 0000000000..d680787b22 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsListItem.tsx @@ -0,0 +1,85 @@ +import { FC } from 'react'; + +import { Box, Dropdown, KebabMenuVertical, Lozenge, Menu, MenuItem, OptOut, Pencil, Skeleton, Text } from 'blocks'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +import { ModalResponse } from 'common'; + +type NotificationSettingsListItemProps = { + setting: ChannelSetting; + loadingSettings: boolean; + modalControl: ModalResponse; + setSettingsToEdit: (settingsToEdit: ChannelSetting) => void; + handleDeleteSetting: (settingToDelete: ChannelSetting) => void; + handleSettingsChange: (setting: ChannelSetting) => void; +}; + +const NotificationSettingsListItem: FC = ({ + setting, + modalControl, + loadingSettings, + setSettingsToEdit, + handleDeleteSetting, +}) => { + const { open } = modalControl; + + return ( + + + + + {setting.description} + + {setting.type == 2 && Range} + {setting.type == 3 && Multi-Range} + + + + } + onClick={() => { + setSettingsToEdit(setting); + open(); + }} + /> + } + onClick={() => { + handleDeleteSetting(setting); + }} + /> + + } + > + + + + + + ); +}; + +export default NotificationSettingsListItem; diff --git a/src/modules/notifSettings/components/NotificationSettingsLists.tsx b/src/modules/notifSettings/components/NotificationSettingsLists.tsx new file mode 100644 index 0000000000..108acaf103 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsLists.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react'; + +import { Box, Separator } from 'blocks'; + +import { ModalResponse } from 'common'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +import NotificationSettingsListItem from './NotificationSettingsListItem'; + +type NotificationSettingsListsProps = { + newSettings: ChannelSetting[]; + loadingSettings: boolean; + modalControl: ModalResponse; + setSettingsToEdit: (settingsToEdit: ChannelSetting) => void; + handleDeleteSetting: (settingToDelete: ChannelSetting) => void; + handleSettingsChange: (setting: ChannelSetting) => void; +}; + +const NotificationSettingsLists: FC = ({ + newSettings, + loadingSettings, + modalControl, + setSettingsToEdit, + handleDeleteSetting, + handleSettingsChange, +}) => { + return ( + + {newSettings.map((setting: ChannelSetting, index: number) => { + return ( + + + + + ); + })} + + ); +}; + +export { NotificationSettingsLists }; diff --git a/src/modules/notifSettings/components/NotificationSettingsModal.tsx b/src/modules/notifSettings/components/NotificationSettingsModal.tsx new file mode 100644 index 0000000000..6fd61f5734 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsModal.tsx @@ -0,0 +1,84 @@ +import { FC } from 'react'; + +import { Modal } from 'blocks'; + +import { ModalResponse } from 'common'; + +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +import { AddNotificationSettingsModalContent } from './AddNotificationSettingsModalContent'; +import { EditNotificationSettingsFormProvider } from '../EditNotificationSetting.form'; + +type NotificationSettingsModalProps = { + modalControl: ModalResponse; + settingsToEdit: ChannelSetting; + setNewSettings: (setting: ChannelSetting[]) => void; + handleSettingsChange: (setting: ChannelSetting) => void; +}; +const NotificationSettingsModal: FC = ({ + modalControl, + settingsToEdit, + handleSettingsChange, +}) => { + const { isOpen, onClose } = modalControl; + + const handleAddSettings = (values: NotificationSettingFormValues) => { + const index = settingsToEdit.index !== 0 ? settingsToEdit.index : Math.floor(Math.random() * 1000000); + + const newAddedSettings: ChannelSetting = values.enableRange + ? values.enableMultiRange + ? { + type: 3, + default: { + lower: Number(values.multirangelowerlimit), + upper: Number(values.multirangeupperlimit), + }, + enabled: values.isDefault, + description: values.settingName, + lowerLimit: Number(values.rangelowerlimit), + upperLimit: Number(values.rangeupperlimit), + ticker: Number(values.sliderStepValue), + index: index, + } + : { + type: 2, + default: Number(values.defaultValue), + enabled: values.isDefault, + description: values.settingName, + lowerLimit: Number(values.rangelowerlimit), + upperLimit: Number(values.rangeupperlimit), + ticker: Number(values.sliderStepValue), + index: index, + } + : { + type: 1, + default: values.isDefault, + description: values.settingName, + index: index, + }; + + handleSettingsChange(newAddedSettings); + + onClose(); + }; + + return ( + + { + handleAddSettings(values); + }} + > + + + + ); +}; + +export { NotificationSettingsModal }; diff --git a/src/modules/notifSettings/components/NotificationSettingsRangeSelector.tsx b/src/modules/notifSettings/components/NotificationSettingsRangeSelector.tsx new file mode 100644 index 0000000000..48a0ff7348 --- /dev/null +++ b/src/modules/notifSettings/components/NotificationSettingsRangeSelector.tsx @@ -0,0 +1,227 @@ +import { useMemo, useState } from 'react'; + +import { Box, Text, TextInput } from 'blocks'; + +import Checkbox from 'components/reusables/checkbox/Checkbox'; + +import InputSlider from 'components/reusables/sliders/InputSlider'; +import RangeSlider from 'components/reusables/sliders/RangeSlider'; + +import { useEditNotificationSettingsForm } from '../EditNotificationSetting.form'; + +const NotificationSettingsRangeSelector = () => { + const { values: formValues, handleChange, setFieldValue } = useEditNotificationSettingsForm(); + + const showPreview = useMemo(() => { + return ( + formValues.rangelowerlimit > 0 && + formValues.rangeupperlimit > 0 && + (formValues.enableMultiRange + ? formValues.multirangelowerlimit > 0 && formValues.multirangeupperlimit > 0 + : formValues.defaultValue > 0) && + formValues.sliderStepValue > 0 && + Number(formValues.rangelowerlimit) <= Number(formValues.rangeupperlimit) && + Number(formValues.sliderStepValue) > 0 && + Number(formValues.sliderStepValue) <= Number(formValues.rangeupperlimit) - Number(formValues.rangelowerlimit) && + (formValues.enableMultiRange + ? Number(formValues.multirangelowerlimit) >= Number(formValues.rangelowerlimit) && + Number(formValues.multirangeupperlimit) <= Number(formValues.rangeupperlimit) && + Number(formValues.multirangeupperlimit) > Number(formValues.multirangelowerlimit) + : Number(formValues.defaultValue) >= Number(formValues.rangelowerlimit) && + Number(formValues.defaultValue) <= Number(formValues.rangeupperlimit)) + ); + }, [ + formValues.rangelowerlimit, + formValues.rangeupperlimit, + formValues.defaultValue, + formValues.sliderStepValue, + formValues.multirangelowerlimit, + formValues.multirangeupperlimit, + formValues.enableMultiRange, + ]); + + const [sliderPreviewVal, setSliderPreviewVal] = useState(formValues.defaultValue); + const [sliderPreviewStartVal, setSliderPreviewStartVal] = useState(formValues.multirangelowerlimit); + const [sliderPreviewEndVal, setSliderPreviewEndVal] = useState(formValues.multirangeupperlimit); + + return ( + <> + + Range Values + + + to + { + setFieldValue('rangeupperlimit', e.target.value); + }} + /> + + + + + setFieldValue('enableMultiRange', !formValues.enableMultiRange)} + /> + + + Enable Multi Range + + User can select a range of values in the slider + + + + {!formValues.enableMultiRange && ( + { + setSliderPreviewVal(Number(e.target.value)); + setFieldValue('defaultValue', e.target.value); + }} + label="Default Value" + /> + )} + + {formValues.enableMultiRange && ( + + Range Values + + { + setFieldValue('multirangelowerlimit', e.target.value); + setSliderPreviewStartVal(Number(e.target.value)); + }} + /> + to + { + setFieldValue('multirangeupperlimit', e.target.value); + setSliderPreviewEndVal(Number(e.target.value)); + }} + /> + + + )} + + + { + handleChange('sliderStepValue'); + setFieldValue('sliderStepValue', e.target.value); + }} + label="Slider Step Value" + /> + + + {showPreview && ( + + Preview + + {!formValues.enableMultiRange && ( + + {formValues.rangelowerlimit} + + { + setSliderPreviewVal(x); + }} + preview={true} + /> + + {formValues.rangeupperlimit} + + )} + + {formValues.enableMultiRange && ( + + {formValues.rangelowerlimit} + + { + setSliderPreviewStartVal(startVal); + setSliderPreviewEndVal(endVal); + }} + preview={true} + /> + + {formValues.rangeupperlimit} + + )} + + )} + + ); +}; + +export { NotificationSettingsRangeSelector }; diff --git a/src/pages/NotificationSettingsPage.tsx b/src/pages/NotificationSettingsPage.tsx new file mode 100644 index 0000000000..b96727078a --- /dev/null +++ b/src/pages/NotificationSettingsPage.tsx @@ -0,0 +1,12 @@ +import { ContentLayout } from 'common'; +import { NotificationSettings } from 'modules/notifSettings/NotificationSettings'; + +const NotificationSettingsPage = () => { + return ( + + + + ); +}; + +export default NotificationSettingsPage; diff --git a/src/queries/hooks/index.ts b/src/queries/hooks/index.ts index 6442083f4e..af7cf2c265 100644 --- a/src/queries/hooks/index.ts +++ b/src/queries/hooks/index.ts @@ -4,3 +4,4 @@ export * from './user'; export * from './rewards'; export * from './pointsVault'; export * from './analytics'; +export * from './notificationSettings'; diff --git a/src/queries/hooks/notificationSettings/index.ts b/src/queries/hooks/notificationSettings/index.ts new file mode 100644 index 0000000000..77a2c6839a --- /dev/null +++ b/src/queries/hooks/notificationSettings/index.ts @@ -0,0 +1 @@ +export * from './useCreateNotificationSettings'; diff --git a/src/queries/hooks/notificationSettings/useCreateNotificationSettings.ts b/src/queries/hooks/notificationSettings/useCreateNotificationSettings.ts new file mode 100644 index 0000000000..bf76f4faa3 --- /dev/null +++ b/src/queries/hooks/notificationSettings/useCreateNotificationSettings.ts @@ -0,0 +1,9 @@ +import { useMutation } from '@tanstack/react-query'; +import { addSettings } from 'queries/queryKeys'; +import { addNotificationSettings } from 'queries/services'; + +export const useCreateNotificationSettings = () => + useMutation({ + mutationKey: [addSettings], + mutationFn: addNotificationSettings, + }); diff --git a/src/queries/queryKeys.ts b/src/queries/queryKeys.ts index ec74ac79f5..fb81779d99 100644 --- a/src/queries/queryKeys.ts +++ b/src/queries/queryKeys.ts @@ -1,5 +1,6 @@ export const UserRewardsDetails = 'userRewardsDetails'; export const addDelegate = 'addDelegate'; +export const addSettings = 'addSettings'; export const addNewSubgraph = 'addNewSubgraph'; export const aliasInfo = 'aliasInfo'; export const allActivities = 'allActivities'; diff --git a/src/queries/services/index.ts b/src/queries/services/index.ts index 18dae290cf..632b38dfaa 100644 --- a/src/queries/services/index.ts +++ b/src/queries/services/index.ts @@ -4,3 +4,4 @@ export * from './rewards'; export * from './pointsVault'; export * from './createChannel'; export * from './analytics'; +export * from './notificationSettings'; diff --git a/src/queries/services/notificationSettings/addNotificationSettings.ts b/src/queries/services/notificationSettings/addNotificationSettings.ts new file mode 100644 index 0000000000..e4acb2d0c4 --- /dev/null +++ b/src/queries/services/notificationSettings/addNotificationSettings.ts @@ -0,0 +1,8 @@ +import { AddNotificationSettingsProps } from 'queries/types'; + +export const addNotificationSettings = async ({ + userPushSDKInstance, + settings, +}: AddNotificationSettingsProps): Promise<{ + transactionHash: any; +}> => await userPushSDKInstance.channel.setting(settings); diff --git a/src/queries/services/notificationSettings/index.ts b/src/queries/services/notificationSettings/index.ts new file mode 100644 index 0000000000..6036f70337 --- /dev/null +++ b/src/queries/services/notificationSettings/index.ts @@ -0,0 +1 @@ +export * from './addNotificationSettings'; diff --git a/src/queries/types/index.ts b/src/queries/types/index.ts index 5f30e3836e..a99590a6c3 100644 --- a/src/queries/types/index.ts +++ b/src/queries/types/index.ts @@ -3,3 +3,4 @@ export * from './user'; export * from './rewards'; export * from './pointsVault'; export * from './createChannel'; +export * from './notificationsettings'; diff --git a/src/queries/types/notificationsettings.ts b/src/queries/types/notificationsettings.ts new file mode 100644 index 0000000000..7dfb3977e8 --- /dev/null +++ b/src/queries/types/notificationsettings.ts @@ -0,0 +1,6 @@ +import { ChannelSetting } from 'modules/channelDashboard/ChannelDashboard.types'; + +export type AddNotificationSettingsProps = { + userPushSDKInstance: any; + settings: ChannelSetting[]; +}; diff --git a/src/structure/MasterInterfacePage.tsx b/src/structure/MasterInterfacePage.tsx index aa796d0cf3..3aa5d59373 100644 --- a/src/structure/MasterInterfacePage.tsx +++ b/src/structure/MasterInterfacePage.tsx @@ -33,6 +33,7 @@ const NotAvailablePage = lazy(() => import('pages/NotAvailablePage')); const NotFoundPage = lazy(() => import('pages/NotFoundPage')); const ReceiveNotifsPage = lazy(() => import('pages/ReceiveNotifsPage')); const NotifSettingsPage = lazy(() => import('pages/NotifSettingsPage')); +const NotificationSettingsPage = lazy(() => import('pages/NotificationSettingsPage')); const SendNotifsPage = lazy(() => import('pages/SendNotifsPage')); const SpacePage = lazy(() => import('pages/SpacePage')); const SupportPage = lazy(() => import('pages/SupportPage')); @@ -328,9 +329,13 @@ function MasterInterfacePage() { path={APP_PATHS.UserSettings} element={} /> - } + /> */} + } />