Amount for Staking
@@ -115,7 +115,7 @@ const TabSpace = styled.div`
border-radius: 20px;
background-color: #f4f5fa;
align-items: center;
- transform: translateY(40px);
+ z-index: 1;
@media ${device.tablet} {
width: 100%;
diff --git a/src/components/channel/AddSettingModalContent.tsx b/src/components/channel/AddSettingModalContent.tsx
new file mode 100644
index 0000000000..bebae1afd3
--- /dev/null
+++ b/src/components/channel/AddSettingModalContent.tsx
@@ -0,0 +1,369 @@
+// React + Web3 Essentials
+import React, { useState } from 'react';
+
+// External Packages
+import styled, { useTheme } from 'styled-components';
+import { useClickAway } from 'react-use';
+import { MdClose } from 'react-icons/md';
+
+// Internal Components
+import ModalConfirmButton from 'primaries/SharedModalComponents/ModalConfirmButton';
+import { ModalInnerComponentType } from 'hooks/useModalBlur';
+import type { ChannelSetting } from '../../helpers/channel/types';
+
+// Internal Configs
+import { device } from 'config/Globals';
+import { Item } from 'components/SharedStyling';
+import { FormSubmision, Input, Span } from 'primaries/SharedStyling';
+import { IOSSwitch } from 'components/SendNotifications';
+import { isAllFilledAndValid } from 'helpers/channel/InputValidation';
+
+const ToggleItem = ({ checked, onChange, label, description }) => {
+ return (
+ -
+
-
+
-
+
+ {description}
+
+
+
+
+ );
+};
+
+interface AddSettingModalProps extends Omit {
+ InnerComponentProps?: {
+ settingToEdit?: ChannelSetting;
+ };
+}
+
+const AddSettingModalContent = ({
+ onConfirm: onSubmit,
+ onClose,
+ toastObject,
+ InnerComponentProps,
+}: AddSettingModalProps) => {
+ const settingToEdit = InnerComponentProps?.settingToEdit || undefined;
+ const [isLoading, setIsLoading] = useState(false);
+ const [settingName, setSettingName] = useState(settingToEdit ? settingToEdit.description : '');
+ const [isDefault, setIsDefault] = useState(settingToEdit && settingToEdit.default === true);
+ const [isRange, setIsRange] = useState(settingToEdit && settingToEdit.type === 2 ? true : false);
+ const [lowerLimit, setLowerLimit] = useState(
+ settingToEdit && settingToEdit.type === 2 ? settingToEdit.lowerLimit.toString() : ''
+ );
+ const [upperLimit, setUpperLimit] = useState(
+ settingToEdit && settingToEdit.type === 2 ? settingToEdit.upperLimit.toString() : ''
+ );
+ const [defaultValue, setDefaultValue] = useState(
+ settingToEdit && settingToEdit.default ? settingToEdit.default.toString() : ''
+ );
+ const [errorInfo, setErrorInfo] = useState();
+
+ const theme = useTheme();
+
+ const handleClose = () => !isLoading && onClose();
+
+ const containerRef = React.useRef(null);
+ useClickAway(containerRef, () => handleClose());
+
+ const onConfirm = (event) => {
+ event.preventDefault();
+ setIsLoading(true);
+ if (
+ isAllFilledAndValid({
+ setErrorInfo,
+ defaultValue,
+ settingName,
+ lowerLimit,
+ type: isRange ? 2 : 1,
+ upperLimit,
+ })
+ ) {
+ const index = settingToEdit ? settingToEdit.index : Math.floor(Math.random() * 1000000);
+ const settingData: ChannelSetting = isRange
+ ? {
+ type: 2,
+ default: Number(defaultValue),
+ description: settingName,
+ index: index,
+ lowerLimit: Number(lowerLimit),
+ upperLimit: Number(upperLimit),
+ }
+ : {
+ type: 1,
+ default: isDefault,
+ description: settingName,
+ index: index,
+ };
+ onSubmit(settingData);
+ onClose();
+ }
+ setIsLoading(false);
+ };
+
+ return (
+
+
+
+ {settingToEdit ? 'Edit ' : 'Add a '} Setting
+ -
+
-
+
+
+ {50 - settingName.length}
+
+
+ {
+ setSettingName(e.target.value.slice(0, 50));
+ setErrorInfo((prev) => ({ ...prev, settingName: undefined }));
+ }}
+ autocomplete="off"
+ hasError={errorInfo?.settingName ? true : false}
+ />
+ {errorInfo?.settingName}
+
+ setIsDefault((prev) => !prev)}
+ label="Set as default"
+ description="Setting turned on for users by default"
+ />
+ setIsRange((prev) => !prev)}
+ label="Range"
+ description="Set a range for this setting e.g. 1-10"
+ />
+ {isRange && (
+ <>
+ -
+
+
-
+ {
+ setLowerLimit(e.target.value);
+ setErrorInfo((prev) => ({ ...prev, lowerLimit: undefined }));
+ }}
+ autocomplete="off"
+ hasError={errorInfo?.lowerLimit ? true : false}
+ />
+
+ {
+ setUpperLimit(e.target.value);
+ setErrorInfo((prev) => ({ ...prev, upperLimit: undefined }));
+ }}
+ autocomplete="off"
+ hasError={errorInfo?.upperLimit ? true : false}
+ />
+
+ {errorInfo?.lowerLimit}
+ {errorInfo?.upperLimit}
+
+ -
+
+ {
+ setDefaultValue(e.target.value);
+ setErrorInfo((prev) => ({ ...prev, default: undefined }));
+ }}
+ autocomplete="off"
+ hasError={errorInfo?.default ? true : false}
+ />
+ {errorInfo?.default}
+
+ >
+ )}
+
+
+
+ );
+};
+
+const CloseButton = styled(MdClose)`
+ align-self: flex-end;
+ color: ${(props) => props.theme.default.secondaryColor};
+ font-size: 20px;
+`;
+
+const ModalTitle = styled.div`
+ font-size: 24px;
+ font-weight: 500;
+ line-height: 29px;
+ letter-spacing: -0.02em;
+ text-align: center;
+ color: ${(props) => props.theme.default.color};
+`;
+
+const ModalContainer = styled.div`
+ width: 30vw;
+ display: flex;
+ flex-direction: column;
+ margin: 6% 1%;
+ background: ${(props) => props.theme.modalContentBackground};
+ border-radius: 1rem;
+ padding: 1.2% 2%;
+ @media (${device.laptop}) {
+ width: 50vw;
+ }
+ @media (${device.mobileL}) {
+ width: 95vw;
+ }
+`;
+
+const Label = styled.div<{ padding?: string }>`
+ font-style: normal;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 150%;
+ letter-spacing: -0.011em;
+ color: ${(props) => props.theme.default.color};
+ padding: ${(props) => props.padding || '0px'};
+`;
+
+const Description = styled.div`
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 16px;
+ letter-spacing: 0em;
+ text-align: left;
+ color: ${(props) => props.theme.default.secondaryColor};
+`;
+
+const MaxWidthInput = styled(Input)<{ hasError: boolean }>`
+ max-width: 108px;
+ flex: 1;
+ border: ${(props) =>
+ props.hasError ? `1px solid ${props.theme.nfsError}` : `1px solid ${props.theme.default.borderColor}`};
+`;
+
+const InputWithError = styled(Input)<{ hasError: boolean }>`
+ flex: 1;
+ border: ${(props) =>
+ props.hasError ? `1px solid ${props.theme.nfsError}` : `1px solid ${props.theme.default.borderColor}`};
+`;
+
+const ErrorInfo = styled.span`
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 18px;
+ letter-spacing: 0em;
+ text-align: left;
+ color: ${(props) => props.theme.nfsError};
+ margin-top: 4px;
+`;
+
+export default AddSettingModalContent;
diff --git a/src/components/channel/ChannelButtons.tsx b/src/components/channel/ChannelButtons.tsx
new file mode 100644
index 0000000000..53b2a2602b
--- /dev/null
+++ b/src/components/channel/ChannelButtons.tsx
@@ -0,0 +1,86 @@
+// External Packages
+import { AiOutlinePlus } from 'react-icons/ai';
+import { FiSettings } from 'react-icons/fi';
+import styled from 'styled-components';
+
+// Internal Components
+import { Button } from 'components/SharedStyling';
+
+interface ChannelButtonProps {
+ onClick: () => void;
+}
+
+export const AddDelegateButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Add Delegate
+
+ );
+};
+
+export const ManageSettingsButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Manage Settings
+
+ );
+};
+
+export const ModifySettingsButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+ Modify Settings
+
+ );
+};
+
+export const AddSettingButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Add Setting
+
+ );
+};
+
+const ChannelButton = styled(Button)`
+ height: 36px;
+ background: ${(props) => props.theme.default.primaryPushThemeTextColor};
+ color: #fff;
+ z-index: 0;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 17px;
+ border-radius: 8px;
+ padding: 4px 12px 4px 12px;
+`;
+
+const ChannelButtonWhite = styled.button`
+ height: 36px;
+ border: 1px solid ${(props) => props.theme.default.borderColor};
+ background: transparent;
+ color: white;
+ z-index: 0;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 17px;
+ border-radius: 8px;
+ padding: 4px 12px 4px 12px;
+ cursor: pointer;
+`;
+
+const ButtonText = styled.span`
+ margin-left: 8px;
+`;
+
+const TransparentButtonText = styled.span`
+ color: ${(props) => props.theme.default.color};
+`;
+
+const AddButtonIcon = styled(AiOutlinePlus)`
+ font-size: 16px;
+`;
diff --git a/src/components/channel/ChannelInfoHeader.tsx b/src/components/channel/ChannelInfoHeader.tsx
new file mode 100644
index 0000000000..c4aec9368e
--- /dev/null
+++ b/src/components/channel/ChannelInfoHeader.tsx
@@ -0,0 +1,69 @@
+// React + Web3 Essentials
+import React, { CSSProperties } from 'react';
+
+// External Packages
+import styled, { useTheme } from 'styled-components';
+
+// Internal Compoonents
+import { useDeviceWidthCheck } from 'hooks';
+import { Item } from 'primaries/SharedStyling';
+import { Section } from 'components/SharedStyling';
+
+// Internal Configs
+import { device } from 'config/Globals';
+
+interface ChannelInfoHeaderProps {
+ title: string;
+ description: string;
+ Button?: React.ReactNode;
+ style?: CSSProperties;
+}
+
+const ChannelInfoHeader = ({ title, description, Button, style }: ChannelInfoHeaderProps) => {
+ const theme = useTheme();
+ const isMobile = useDeviceWidthCheck(700);
+
+ return (
+
+ -
+ {title}
+ {!isMobile && (
+ <>
+
+ {description}
+ >
+ )}
+
+ {Button}
+
+ );
+};
+
+export default ChannelInfoHeader;
+
+const DelegatesInfoHeader = styled.div`
+ font-weight: 600;
+ font-size: 18px;
+ line-height: 150%;
+ display: flex;
+ align-items: center;
+ color: ${(props) => props.theme.color};
+`;
+
+const DelegatesInfoLabel = styled.div`
+ font-weight: 400;
+ font-size: 15px;
+ line-height: 140%;
+ color: ${(props) => props.theme.default.secondaryColor};
+`;
+
+const HeaderSection = styled(Section)`
+ flex-direction: row;
+ align-items: center;
+ padding: 24px 24px 20px 24px;
+
+ @media ${device.tablet} {
+ padding: 20px 12px;
+ flex: 0;
+ }
+`;
diff --git a/src/components/channel/ChannelInfoList.tsx b/src/components/channel/ChannelInfoList.tsx
new file mode 100644
index 0000000000..84210415cf
--- /dev/null
+++ b/src/components/channel/ChannelInfoList.tsx
@@ -0,0 +1,207 @@
+// React + Web3 Essentials
+import React, { CSSProperties } from 'react';
+
+// External Packages
+import styled from 'styled-components';
+import { useNavigate } from 'react-router-dom';
+
+// Internal Compoonents
+import { Item } from 'primaries/SharedStyling';
+import DelegateInfo from 'components/DelegateInfo';
+import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner';
+import Icon from 'assets/navigation/receiveNotifOffIcon.svg';
+import { ImageV2 } from 'components/reusables/SharedStylingV2';
+import { ModifySettingsButton } from './ChannelButtons';
+import DelegateSettingsDropdown, { ChannelDropdownOption } from './DelegateSettingsDropdown';
+
+// Internal Configs
+import { device } from 'config/Globals';
+import { ChannelSetting } from 'helpers/channel/types';
+
+const isOwner = (account: string, delegate: string) => {
+ return account.toLowerCase() === delegate.toLowerCase();
+};
+
+type ChannelInfoListProps =
+ | {
+ isAddress: true;
+ items: string[];
+ isLoading: boolean;
+ account: string;
+ style?: CSSProperties;
+ addressDropdownOptions: Array;
+ }
+ | {
+ isAddress: false;
+ items: Array;
+ isLoading: boolean;
+ account: string;
+ style?: CSSProperties;
+ settingsDropdownOptions?: Array;
+ };
+
+const ChannelInfoList = (props: ChannelInfoListProps) => {
+ const navigate = useNavigate();
+
+ const handleNavigateToModifySettings = () => {
+ navigate(`/channel/settings`);
+ };
+
+ return (
+
+ -
+ {props.isLoading ? (
+
+
+
+ ) : (
+ <>
+ {props.items &&
+ props.items.length > 0 &&
+ props.items.map((item) => {
+ return (
+
+
+
-
+
-
+ {props.isAddress ? (
+
+
+
+ ) : (
+ <>
+ {item.description}
+ {item.lowerLimit !== undefined && Range}
+ >
+ )}
+ {props.isAddress && isOwner(props.account, item) && Creator}
+
+ {props.isAddress === true &&
+ props.addressDropdownOptions?.length > 0 &&
+ !isOwner(props.account, item) && (
+
+ )}
+ {props.isAddress === false && props.settingsDropdownOptions?.length > 0 && (
+
+ )}
+
+
+ );
+ })}
+ {props.items && props.items.length === 0 && !props.isAddress && (
+
+
+ No settings added
+
+ Add settings for users to customize their notification preferences.
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+export default ChannelInfoList;
+
+const DelegatesList = styled.div<{ isLoading: boolean }>`
+ padding: ${(props) => (props.isLoading ? '0px' : '0px 24px 16px')};
+ flex: 1;
+
+ @media ${device.tablet} {
+ flex: 0;
+ padding: ${(props) => (props.isLoading ? '0px' : '0px 16px 10px')};
+ }
+`;
+
+const Tag = styled.div`
+ padding: 4px 8px 4px 8px;
+ border-radius: 4px;
+ background-color: ${(props) => props.theme.default.secondaryBg};
+ color: ${(props) => props.theme.tooltipContentDesc};
+ font-size: 10px;
+ margin-left: 8px;
+`;
+
+const NotificationSettingName = styled.span`
+ margin-left: 15px;
+ color: ${(props) =>
+ props.theme.scheme === 'light' ? props.theme.default.color : props.theme.default.secondaryColor};
+`;
+
+const EmptyNotificationSetting = styled.div`
+ border-top: ${(props) => `1px solid ${props.theme.default.borderColor}`};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ margin-bottom: 16px;
+`;
+
+const EmptyNotificationTitle = styled.div`
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ letter-spacing: 0em;
+ text-align: left;
+ color: ${(props) => props.theme.default.color};
+`;
+
+const EmptyNotificationDesc = styled.div`
+ margin-top: 1px;
+ margin-bottom: 16px;
+ color: ${(props) => props.theme.default.secondaryColor};
+`;
+
+const NotifIcon = styled(ImageV2)`
+ color: ${(props) => props.theme.default.color};
+ margin-top: 32px;
+ margin-bottom: 12px;
+`;
+
+const Divider = styled.div`
+ background-color: ${(props) => props.theme.default.border};
+ height: 1px;
+`;
+
+const SpinnerContainer = styled.div`
+ height: 100px;
+`;
+
+const DelegateInfoContainer = styled.div`
+ @media ${device.tablet} {
+ margin: 0px 0px 0px 5px;
+ }
+`;
diff --git a/src/components/channel/DelegateSettingsDropdown.tsx b/src/components/channel/DelegateSettingsDropdown.tsx
new file mode 100644
index 0000000000..dfea1b26a3
--- /dev/null
+++ b/src/components/channel/DelegateSettingsDropdown.tsx
@@ -0,0 +1,85 @@
+// React + Web3 Essentials
+import React, { useRef, useState } from 'react';
+
+// External Packages
+import { AiOutlineMore } from 'react-icons/ai';
+import styled from 'styled-components';
+import { useClickAway } from 'react-use';
+
+export interface ChannelDropdownOption {
+ icon: React.ReactNode;
+ text: string;
+ onClick: (item) => void;
+}
+
+interface DelegateSettingsDropdownProps {
+ options: Array;
+ item: string;
+}
+
+const DelegateSettingsDropdown = ({ options, item }: DelegateSettingsDropdownProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef();
+
+ useClickAway(dropdownRef, () => setIsOpen(false));
+
+ return (
+
+ setIsOpen(true)} />
+ {isOpen && (
+ setIsOpen(false)} ref={dropdownRef}>
+ {options.map(({ icon, onClick, text }, index) => {
+ return (
+ onClick(item)}
+ key={index}
+ index={index}
+ >
+ {icon}
+ {text}
+
+ );
+ })}
+
+ )}
+
+ );
+};
+
+export default DelegateSettingsDropdown;
+
+const MoreButtonUI = styled(AiOutlineMore)`
+ background: transparent;
+ display: flex;
+ cursor: pointer;
+ width: 24px;
+ height: 24px;
+ padding: 0px;
+ position: relative;
+ width: 24px;
+ height: 24px;
+ color: ${(props) => props.theme.default.color};
+`;
+
+const ListContainer = styled.div`
+ padding: 10px 6px;
+ width: 119px;
+ border-radius: 8px;
+ border: 1px solid ${(props) => props.theme.default.border};
+ position: absolute;
+ top: 3px;
+ right: 0px;
+ background-color: ${(props) => props.theme.default.bg};
+ z-index: 2;
+`;
+
+const OptionButton = styled.div<{ index: number }>`
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ margin-top: ${(props) => (props.index === 0 ? '0px' : '16px')};
+`;
+
+const OptionText = styled.span`
+ margin-left: 8px;
+`;
diff --git a/src/components/channel/DepositFeeFooter.tsx b/src/components/channel/DepositFeeFooter.tsx
new file mode 100644
index 0000000000..30f49a8480
--- /dev/null
+++ b/src/components/channel/DepositFeeFooter.tsx
@@ -0,0 +1,309 @@
+// React + Web3 Essentials
+import React, { useEffect, useState } from 'react';
+
+// External Packages
+import styled from 'styled-components';
+import { useSelector } from 'react-redux';
+import { MdCheckCircle, MdError } from 'react-icons/md';
+
+// Internal Compoonents
+import { ItemHV2, ItemVV2 } from 'components/reusables/SharedStylingV2';
+import FaucetInfo from 'components/FaucetInfo';
+import useToast from 'hooks/useToast';
+import { useAccount } from 'hooks';
+
+// Internal Configs
+import GLOBALS, { device } from 'config/Globals';
+import { Button } from '../SharedStyling';
+import { LOADER_SPINNER_TYPE } from 'components/reusables/loaders/LoaderSpinner';
+import Spinner from 'components/reusables/spinners/SpinnerUnit';
+import VerifyLogo from '../../assets/Vector.svg';
+import { approvePushToken, getPushTokenApprovalAmount, mintPushToken } from 'helpers';
+import { addresses } from 'config';
+
+interface DepositFeeFooterProps {
+ title: string;
+ description: string;
+ onCancel: () => void;
+ disabled: boolean;
+ onClick: () => void;
+ feeRequired: number;
+}
+
+const DepositFeeFooter = ({ title, description, onCancel, disabled, onClick, feeRequired }: DepositFeeFooterProps) => {
+ const { account, provider } = useAccount();
+ const [pushApprovalAmount, setPushApprovalAmount] = useState(0);
+ const [pushDeposited, setPushDeposited] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const depositFeeToast = useToast();
+
+ useEffect(() => {
+ if (!account || !provider) return;
+
+ (async function () {
+ const pushTokenApprovalAmount = await getPushTokenApprovalAmount({
+ address: account,
+ provider: provider,
+ contractAddress: addresses.epnscore,
+ });
+ setPushApprovalAmount(parseInt(pushTokenApprovalAmount));
+ const amountToBeDeposit = parseInt(pushTokenApprovalAmount);
+
+ if (amountToBeDeposit >= feeRequired && amountToBeDeposit != 0) {
+ setPushDeposited(true);
+ } else {
+ setPushDeposited(false);
+ }
+ })();
+ }, [account, provider]);
+
+ const depositPush = async () => {
+ setIsLoading(true);
+ if (!provider) return;
+ const signer = provider.getSigner(account);
+ depositFeeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
+ try {
+ const response = await approvePushToken({
+ signer,
+ contractAddress: addresses.epnscore,
+ amount: feeRequired - pushApprovalAmount,
+ });
+ console.log('response', response);
+ if (response) {
+ setIsLoading(false);
+ setPushApprovalAmount(feeRequired);
+ setPushDeposited(true);
+ depositFeeToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: 'Successfully approved Push!',
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ }
+ } catch (err) {
+ console.log(err);
+ if (err.code == 'ACTION_REJECTED') {
+ // EIP-1193 userRejectedRequest error
+ depositFeeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `User denied message signature.`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ } else {
+ depositFeeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error in approving PUSH Token`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+
+ console.log('Error --> %o', err);
+ console.log({ err });
+ }
+ }
+ setIsLoading(false);
+ };
+
+ return (
+ <>
+
+ {
+ await mintPushToken({ noOfTokens, provider, account });
+ }}
+ />
+
+ {isLoading ? (
+ <>
+ {/* Verifying Spinner and Text */}
+
+
+ Verifying Transaction
+
+ >
+ ) : (
+ <>
+ {/* This below is Footer Buttons i.e, Cancel and save changes */}
+
+
+ Cancel
+
+
+ {pushApprovalAmount >= feeRequired ? (
+
+ Save Changes
+
+ ) : (
+
+ Approve PUSH
+
+ )}
+
+ >
+ )}
+ >
+ );
+};
+
+export default DepositFeeFooter;
+
+const TickImage = styled.img``;
+
+const Footer = styled(ItemVV2)`
+ background: ${(props) => props.theme.editFooterBg};
+ border-radius: 20px;
+ padding: 23px 32px;
+ display: grid;
+ grid-auto-flow: column;
+ align-content: space-between;
+ justify-content: space-between;
+ grid-gap: 40px;
+ height: 100px;
+ align-items: center;
+ z-index: 1;
+
+ @media ${device.tablet} {
+ padding: 16px;
+ flex: 0;
+ }
+
+ @media ${device.mobileL} {
+ margin: 0px;
+ }
+`;
+
+const FooterPrimaryText = styled.p`
+ margin: 0px;
+ color: ${(props) => props.theme.editChannelPrimaryText};
+ font-style: normal;
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 24px;
+`;
+
+const FooterSecondaryText = styled.p`
+ font-size: 12px;
+ margin: 0px;
+ font-weight: 400;
+ line-height: 130%;
+ color: ${(props) => props.theme.editChannelSecondaryText};
+`;
+
+const EditFee = styled.p`
+ margin: 0px 0px 0px 5px;
+ color: ${(props) => props.theme.viewChannelSecondaryText};
+ font-style: normal;
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 24px;
+`;
+
+const VerifyingContainer = styled(ItemVV2)`
+ flex-direction: row;
+ margin-top: 33px;
+
+ @media ${device.tablet} {
+ flex: 0;
+ }
+`;
+
+const TransactionText = styled.p`
+ font-style: normal;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 22px;
+ display: flex;
+ align-items: center;
+ margin-left: 12px;
+ color: ${(props) => props.theme.editChannelPrimaryText};
+`;
+
+const ButtonContainer = styled(ItemHV2)`
+ justify-content: end;
+ margin-top: 24px;
+ @media ${device.mobileL} {
+ flex-direction: column-reverse;
+ flex: 0;
+ }
+`;
+
+const FooterButtons = styled(Button)<{ disabled: boolean }>`
+ font-style: normal;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 22px;
+ display: flex;
+ border-radius: 15px;
+ align-items: center;
+ text-align: center;
+ background: ${(props) => (props.disabled ? props.theme.nfsDisabled : props.theme.default.primaryPushThemeTextColor)};
+ color: ${(props) => (props.disabled ? props.theme.nfsDisabledText : 'white')};
+ padding: 16px 27px;
+ width: 12rem;
+
+ @media ${device.tablet} {
+ font-size: 15px;
+ padding: 12px 12px;
+ width: 8rem;
+ }
+
+ @media ${device.mobileL} {
+ width: -webkit-fill-available;
+ }
+`;
+
+const CancelButtons = styled(FooterButtons)`
+ margin-right: 14px;
+ background: ${(props) => props.theme.default.bg};
+ color: ${(props) => props.theme.logoBtnColor};
+ border: 1px solid ${(props) =>
+ props.theme.scheme === 'light'
+ ? props.theme.default.primaryPushThemeTextColor
+ : props.theme.default.borderColor};
+
+ @media ${device.mobileL} {
+ margin-right: 0px;
+ margin-top: 10px;
+ }
+`;
diff --git a/src/components/channel/NotificationSettings.tsx b/src/components/channel/NotificationSettings.tsx
new file mode 100644
index 0000000000..cd68c06845
--- /dev/null
+++ b/src/components/channel/NotificationSettings.tsx
@@ -0,0 +1,286 @@
+// React + Web3 Essentials
+import React, { useEffect, useMemo } from 'react';
+import { ethers } from 'ethers';
+
+// External Packages
+import 'react-dropdown/style.css';
+import { useSelector } from 'react-redux';
+import 'react-toastify/dist/ReactToastify.min.css';
+import { useNavigate } from 'react-router-dom';
+import { PiPencilSimpleBold } from 'react-icons/pi';
+import { IoMdRemoveCircleOutline } from 'react-icons/io';
+import { MdCheckCircle, MdError } from 'react-icons/md';
+
+// Internal Compoonents
+import useToast from '../../hooks/useToast';
+import AddSettingModalContent from './AddSettingModalContent';
+import ChannelInfoHeader from './ChannelInfoHeader';
+import { AddSettingButton } from './ChannelButtons';
+import ChannelInfoList from './ChannelInfoList';
+import DepositFeeFooter from './DepositFeeFooter';
+import { useAccount } from 'hooks';
+
+// Internal Configs
+import { appConfig } from 'config';
+import useModalBlur, { MODAL_POSITION } from 'hooks/useModalBlur';
+import { ChannelSetting } from 'helpers/channel/types';
+import { getChannel } from 'services';
+
+// Constants
+const CORE_CHAIN_ID = appConfig.coreContractChain;
+
+function NotificationSettings() {
+ const { account, chainId } = useAccount();
+ const { coreChannelAdmin, delegatees } = useSelector((state: any) => state.admin);
+ const { epnsWriteProvider } = useSelector((state: any) => state.contracts);
+
+ const onCoreNetwork = CORE_CHAIN_ID === chainId;
+ const EDIT_SETTING_FEE = 50;
+
+ const [channelAddress, setChannelAddress] = React.useState('');
+ const [settings, setSettings] = React.useState([]);
+ const [settingToEdit, setSettingToEdit] = React.useState(undefined);
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [currentSettings, setCurrentSettings] = React.useState([]);
+ const [isLoadingSettings, setIsLoadingSettings] = React.useState(true);
+
+ const {
+ isModalOpen: isAddSettingModalOpen,
+ showModal: showAddSettingModal,
+ ModalComponent: AddSettingModal,
+ } = useModalBlur();
+
+ const redirectBack = () => {
+ const url = window.location.origin;
+ window.location.replace(`${url}/channels`);
+ };
+
+ useEffect(() => {
+ // Is not the channel admin so cannot edit settings
+ (async () => {
+ setIsLoading(true);
+ if (!account) return;
+ try {
+ const channelDetails = await getChannel({ channel: account });
+ if (!channelDetails) redirectBack();
+ } catch {
+ redirectBack();
+ }
+ if (coreChannelAdmin && coreChannelAdmin !== account) redirectBack();
+ setIsLoading(false);
+ })();
+ }, [account, coreChannelAdmin]);
+
+ useEffect(() => {
+ if (isAddSettingModalOpen === false) setSettingToEdit(undefined);
+ }, [isAddSettingModalOpen]);
+
+ React.useEffect(() => {
+ if (!account) return;
+ if (!delegatees || !delegatees.length) {
+ setChannelAddress(account);
+ } else {
+ // default the channel address to the first one on the list which should be that of the user if they have a channel
+ if (onCoreNetwork) setChannelAddress(delegatees[0].channel);
+ else setChannelAddress(delegatees[0].alias_address);
+ }
+ }, [delegatees, account]);
+
+ useEffect(() => {
+ if (delegatees) {
+ const delegatee = delegatees.find(({ channel }) => channel === channelAddress);
+ if (delegatee) {
+ const { channel_settings } = delegatee;
+ if (channel_settings !== null) {
+ const settings = JSON.parse(channel_settings);
+ setSettings(settings);
+ setCurrentSettings(settings);
+ setIsLoadingSettings(false);
+ }
+ }
+ }
+ return null;
+ }, [delegatees, channelAddress]);
+
+ // Notification Toast
+ const notificationToast = useToast(5000);
+
+ const navigate = useNavigate();
+
+ const goBack = () => {
+ navigate('/dashboard', { replace: true });
+ };
+
+ const addSetting = (newSetting: ChannelSetting) => {
+ const index = settings.findIndex((setting) => setting.index === newSetting.index);
+ if (index === -1) setSettings([...settings, newSetting]);
+ else {
+ const updatedSetting = [...settings];
+ updatedSetting[index] = newSetting;
+ setSettings(updatedSetting);
+ }
+ };
+
+ const editSetting = (settingToEdit: ChannelSetting) => {
+ setSettingToEdit(settingToEdit);
+ showAddSettingModal();
+ };
+
+ const deleteSetting = (settingToDelete: ChannelSetting) => {
+ setSettings((settings) => settings.filter((setting) => setting.index !== settingToDelete.index));
+ };
+
+ const saveSettings = async () => {
+ try {
+ setIsLoading(true);
+
+ const feesRequiredForEdit = 50;
+ const parsedFees = ethers.utils.parseUnits(feesRequiredForEdit.toString(), 18);
+
+ notificationToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
+ const notifOptions = ethers.utils.parseUnits('2', 18);
+ let _notifSettings = '';
+ let _notifDescription = '';
+ settings.forEach((setting) => {
+ if (_notifSettings !== '') _notifSettings += '+';
+ if (_notifDescription !== '') _notifDescription += '+';
+ if (setting.type === 1) {
+ _notifSettings += `${setting.type}-${setting.default ? '1' : '0'}`;
+ } else if (setting.type === 2) {
+ _notifSettings += `${setting.type}-${setting.default}-${setting.lowerLimit}-${setting.upperLimit}`;
+ }
+ _notifDescription += setting.description;
+ });
+
+ const tx = await epnsWriteProvider.createChannelSettings(
+ notifOptions,
+ _notifSettings,
+ _notifDescription,
+ parsedFees,
+ { gasLimit: 1000000 }
+ );
+
+ console.log(tx);
+ await tx.wait();
+ setCurrentSettings(settings);
+ setIsLoading(false);
+
+ notificationToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: `Channel Settings Updated Successfully`,
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ } catch (err) {
+ setIsLoading(false);
+ console.log(err.message);
+ if (err.code == 'ACTION_REJECTED') {
+ // EIP-1193 userRejectedRequest error
+ notificationToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `User denied message signature.`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ } else {
+ notificationToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error in updating channel settings`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ console.log('Error --> %o', err);
+ console.log({ err });
+ }
+ }
+ };
+
+ const settingsChanged = useMemo(() => {
+ if (!settings || !currentSettings) return false;
+ console.log('Settings changed bro', settings, currentSettings);
+ if (settings.length !== currentSettings.length) return true;
+ let isUnchanged = true;
+ for (let i = 0; i < settings.length; i++) {
+ const setting1 = settings[i];
+ const setting2 = currentSettings[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.lowerLimit === setting2.lowerLimit &&
+ setting1.upperLimit === setting2.upperLimit;
+ }
+ }
+ return isUnchanged === false;
+ }, [settings, currentSettings]);
+
+ return (
+ <>
+ }
+ />
+ ,
+ onClick: editSetting,
+ text: 'Edit',
+ },
+ {
+ icon: ,
+ onClick: deleteSetting,
+ text: 'Delete',
+ },
+ ]}
+ />
+
+
+ >
+ );
+}
+
+// Export Default
+export default NotificationSettings;
diff --git a/src/config/Themization.js b/src/config/Themization.js
index c9f7012a7e..4abc8d45cb 100644
--- a/src/config/Themization.js
+++ b/src/config/Themization.js
@@ -276,6 +276,10 @@ const themeLight = {
snapSecondaryText:'#62626a',
snapBackground:'#F2F2F2',
+ // Notification Settings
+ nfsError: '#ED5858',
+ nfsDisabled: '#DFDEE9',
+ nfsDisabledText: '#AFB3BF',
};
const themeDark = {
@@ -560,6 +564,10 @@ const themeDark = {
snapSecondaryText:'#787E99',
snapBackground:'#404650',
+ // Notification Settings
+ nfsError: '#ED5858',
+ nfsDisabled: '#AFB3BF',
+ nfsDisabledText: '#787E99',
};
module.exports = {
diff --git a/src/helpers/channel/InputValidation.ts b/src/helpers/channel/InputValidation.ts
new file mode 100644
index 0000000000..eaa0d7843e
--- /dev/null
+++ b/src/helpers/channel/InputValidation.ts
@@ -0,0 +1,96 @@
+import { ChannelSetting } from './types';
+
+const isEmpty = (field: string) => {
+ return field.trim().length == 0;
+};
+
+export const isAllFilledAndValid = ({
+ setErrorInfo,
+ lowerLimit,
+ upperLimit,
+ type,
+ settingName,
+ defaultValue,
+}: {
+ setErrorInfo: React.Dispatch<
+ React.SetStateAction<{
+ settingName: string;
+ lowerLimit: string;
+ upperLimit: string;
+ default: string;
+ }>
+ >;
+ upperLimit: string;
+ lowerLimit: string;
+ type: ChannelSetting['type'];
+ settingName: string;
+ defaultValue: string;
+}): boolean => {
+ setErrorInfo(undefined);
+
+ let hasError = false;
+
+ if (isEmpty(settingName)) {
+ setErrorInfo((x) => ({
+ ...x,
+ settingName: 'Setting Name is required',
+ }));
+ hasError = true;
+ }
+
+ if (type === 2) {
+ if (isEmpty(lowerLimit)) {
+ setErrorInfo((x) => ({
+ ...x,
+ lowerLimit: 'Minimum range is required',
+ }));
+ hasError = true;
+ }
+ if (isEmpty(upperLimit)) {
+ setErrorInfo((x) => ({
+ ...x,
+ upperLimit: 'Maximum range is required',
+ }));
+ hasError = true;
+ }
+ if (isEmpty(defaultValue)) {
+ setErrorInfo((x) => ({
+ ...x,
+ default: 'Default value is required',
+ }));
+ hasError = true;
+ }
+ if (!isEmpty(lowerLimit) && !isEmpty(upperLimit) && !isEmpty(defaultValue)) {
+ if (Number(lowerLimit) < 0) {
+ setErrorInfo((x) => ({
+ ...x,
+ lowerLimit: 'Minimum range should be greater than 0',
+ }));
+ hasError = true;
+ }
+ if (Number(upperLimit) < 0) {
+ setErrorInfo((x) => ({
+ ...x,
+ upperLimit: 'Maximum range should be greater than 0',
+ }));
+ hasError = true;
+ }
+ if (Number(lowerLimit) > Number(upperLimit)) {
+ setErrorInfo((x) => ({
+ ...x,
+ lowerLimit: 'Minimum range should be less than maximum range',
+ }));
+ hasError = true;
+ }
+ if (Number(defaultValue) < Number(lowerLimit) || Number(defaultValue) > Number(upperLimit)) {
+ setErrorInfo((x) => ({
+ ...x,
+ default: 'Default value not in range',
+ }));
+ hasError = true;
+ }
+ }
+ }
+ console.log('Has error', hasError);
+ return !hasError;
+};
diff --git a/src/helpers/channel/types.ts b/src/helpers/channel/types.ts
new file mode 100644
index 0000000000..09616ad0bc
--- /dev/null
+++ b/src/helpers/channel/types.ts
@@ -0,0 +1,15 @@
+export type ChannelSetting =
+ | {
+ type: 1; // Boolean
+ default: boolean;
+ description: string;
+ index: number;
+ }
+ | {
+ type: 2; // Range
+ default: number;
+ description: string;
+ index: number;
+ lowerLimit: number;
+ upperLimit: number;
+ };
diff --git a/src/modules/channelDashboard/channelDashboardModule.tsx b/src/modules/channelDashboard/channelDashboardModule.tsx
index 7b09a56982..fa0c72da2d 100644
--- a/src/modules/channelDashboard/channelDashboardModule.tsx
+++ b/src/modules/channelDashboard/channelDashboardModule.tsx
@@ -60,7 +60,7 @@ const Container = styled(Section)`
100% - ${globalsMargin.MINI_MODULES.DESKTOP.RIGHT} - ${globalsMargin.MINI_MODULES.DESKTOP.LEFT} -
${GLOBALS.ADJUSTMENTS.PADDING.HUGE} - ${GLOBALS.ADJUSTMENTS.PADDING.HUGE}
);
- padding: ${GLOBALS.ADJUSTMENTS.PADDING.HUGE};
+ padding: ${GLOBALS.ADJUSTMENTS.PADDING.DEFAULT};
position: relative;
margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.DESKTOP};
diff --git a/src/modules/editChannel/EditChannel.tsx b/src/modules/editChannel/EditChannel.tsx
index ea558e4e05..411d40530c 100644
--- a/src/modules/editChannel/EditChannel.tsx
+++ b/src/modules/editChannel/EditChannel.tsx
@@ -533,6 +533,7 @@ const Footer = styled(ItemVV2)`
justify-content: space-between;
grid-gap: 40px;
margin-top:35px;
+ z-index: 1;
@media (max-width:600px){
padding: 16px;
diff --git a/src/modules/notifSettings/NotifSettingsModule.tsx b/src/modules/notifSettings/NotifSettingsModule.tsx
new file mode 100644
index 0000000000..59254f1418
--- /dev/null
+++ b/src/modules/notifSettings/NotifSettingsModule.tsx
@@ -0,0 +1,86 @@
+// React + Web3 Essentials
+import React from 'react';
+
+// External Packages
+import ReactGA from 'react-ga';
+import styled from 'styled-components';
+import { Navigate } from 'react-router-dom';
+
+// Internal Components
+import NotificationSettings from 'components/channel/NotificationSettings';
+import { Section } from 'primaries/SharedStyling';
+
+// Internal Configs
+import { appConfig } from 'config';
+import GLOBALS, { device, globalsMargin } from 'config/Globals';
+
+// Constants
+export const ALLOWED_CORE_NETWORK = appConfig.coreContractChain; //chainId of network which we have deployed the core contract on
+
+// Create Header
+function NotifSettingsPage() {
+ ReactGA.pageview('/channel/settings');
+
+ // toast related section
+ const [toast, showToast] = React.useState(null);
+ const clearToast = () => showToast(null);
+
+ //clear toast variable after it is shown
+ React.useEffect(() => {
+ if (toast) {
+ clearToast();
+ }
+ }, [toast]);
+ // toast related section
+
+ // Render
+ return (
+
+
+
+ );
+}
+
+// Define how the module is fitted, define it align-self to strect to fill entire bounds
+// Define height: inherit to cover entire height
+const Container = styled(Section)`
+ align-items: center;
+ align-self: center;
+ background: ${(props) => props.theme.default.bg};
+ border-radius: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE}
+ ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE};
+ box-shadow: ${GLOBALS.ADJUSTMENTS.MODULE_BOX_SHADOW};
+ display: flex;
+ flex-direction: column;
+ flex: initial;
+ justify-content: center;
+ max-width: 1200px;
+ width: calc(
+ 100% - ${globalsMargin.MINI_MODULES.DESKTOP.RIGHT} - ${globalsMargin.MINI_MODULES.DESKTOP.LEFT} -
+ ${GLOBALS.ADJUSTMENTS.PADDING.BIG} - ${GLOBALS.ADJUSTMENTS.PADDING.BIG}
+ );
+ position: relative;
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.DESKTOP};
+ padding: ${GLOBALS.ADJUSTMENTS.PADDING.BIG};
+
+ @media ${device.laptop} {
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.TABLET};
+ padding: ${GLOBALS.ADJUSTMENTS.PADDING.DEFAULT};
+ justify-content: flex-start;
+ }
+
+ @media ${device.mobileL} {
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.BIG_MODULES.MOBILE};
+ padding: ${GLOBALS.ADJUSTMENTS.PADDING.DEFAULT};
+ width: calc(
+ 100% - ${globalsMargin.MINI_MODULES.MOBILE.RIGHT} - ${globalsMargin.MINI_MODULES.MOBILE.LEFT} -
+ ${GLOBALS.ADJUSTMENTS.PADDING.DEFAULT} - ${GLOBALS.ADJUSTMENTS.PADDING.DEFAULT}
+ );
+ min-height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.BIG_MODULES.MOBILE.TOP});
+ overflow-y: scroll;
+ border-radius: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} 0 0;
+ }
+`;
+
+// Export Default
+export default NotifSettingsPage;
diff --git a/src/pages/NotifSettingsPage.tsx b/src/pages/NotifSettingsPage.tsx
new file mode 100644
index 0000000000..1027f064f3
--- /dev/null
+++ b/src/pages/NotifSettingsPage.tsx
@@ -0,0 +1,29 @@
+// React + Web3 Essentials
+import React from "react";
+
+// External Packages
+import styled from 'styled-components';
+
+// Internal Components
+import { SectionV2 } from 'components/reusables/SharedStylingV2';
+import NotificationSettings from "modules/notifSettings/NotifSettingsModule";
+
+// Page structure
+const SendNotifsPage = () => {
+ // RENDER
+ return (
+
+
+
+ );
+}
+export default SendNotifsPage;
+
+// This defines the page settings, toggle align-self to center if not covering entire stuff, align-items to place them at center
+// justify content flex start to start from top, height is defined by module as well as amount of margin, padding
+const Container = styled(SectionV2)`
+ flex: 1;
+ flex-direction: column;
+ align-self: stretch;
+ justify-content: flex-start;
+`;
\ No newline at end of file
diff --git a/src/primaries/SharedModalComponents/ModalConfirmButton.tsx b/src/primaries/SharedModalComponents/ModalConfirmButton.tsx
index b08ca2fd2f..adb907c004 100644
--- a/src/primaries/SharedModalComponents/ModalConfirmButton.tsx
+++ b/src/primaries/SharedModalComponents/ModalConfirmButton.tsx
@@ -10,16 +10,17 @@ import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderS
// Types
type ModalConfirmButtonType = {
text:string,
- onClick: ()=>void,
+ onClick?: ()=>void,
isLoading: boolean,
color?:string,
backgroundColor?:string,
border?:string,
topMargin?:string,
loaderTitle?: string,
+ padding?:string,
}
-const ModalConfirmButton = ({text, onClick, isLoading,color,backgroundColor,border,topMargin,loaderTitle}:ModalConfirmButtonType)=>{
+const ModalConfirmButton = ({text, onClick, isLoading,color,backgroundColor,border,topMargin,loaderTitle,padding}:ModalConfirmButtonType)=>{
const themes = useTheme();
return(
@@ -46,6 +47,7 @@ const ModalConfirmButton = ({text, onClick, isLoading,color,backgroundColor,bord
color={color}
backgroundColor={backgroundColor}
border={border}
+ style={{ padding: padding ? padding : "16px" }}
>
{text}
@@ -87,8 +89,6 @@ const CustomButton = styled.button`
background-color:${props => props.backgroundColor || '#CF1C84'};
border:${props=>props.border || '1px solid transparent'};
border-radius:15px;
- // padding: 5% 12%;
- padding:16px;
`;
export default ModalConfirmButton
\ No newline at end of file
diff --git a/src/services/channels/getChannel.ts b/src/services/channels/getChannel.ts
new file mode 100644
index 0000000000..ce3ebd2ebc
--- /dev/null
+++ b/src/services/channels/getChannel.ts
@@ -0,0 +1,19 @@
+// External Packages
+import * as PushAPI from '@pushprotocol/restapi';
+
+// Internal Configs
+import { appConfig } from 'config';
+
+// Types
+type Props = {
+ channel: string;
+};
+
+export const getChannel = async ({ channel }: Props) => {
+ try {
+ return await PushAPI.channels.getChannel({ channel, env: appConfig.appEnv });
+ } catch (err) {
+ console.error(err);
+ throw new Error(err.message);
+ }
+};
diff --git a/src/services/channels/index.ts b/src/services/channels/index.ts
index 9073c8ed58..03752f1c6c 100644
--- a/src/services/channels/index.ts
+++ b/src/services/channels/index.ts
@@ -1,3 +1,4 @@
export * from "./getChannelDelegates";
export * from "./getChannels";
-export * from "./getChannelsSearch";
\ No newline at end of file
+export * from "./getChannelsSearch";
+export * from "./getChannel";
diff --git a/src/structure/MasterInterfacePage.tsx b/src/structure/MasterInterfacePage.tsx
index b34e610825..56f0ead8df 100644
--- a/src/structure/MasterInterfacePage.tsx
+++ b/src/structure/MasterInterfacePage.tsx
@@ -23,6 +23,7 @@ const InternalDevPage = lazy(() => import('pages/InternalDevPage'));
const NFTPage = lazy(() => import('pages/NFTPage'));
const NotAvailablePage = lazy(() => import('pages/NotAvailablePage'));
const ReceiveNotifsPage = lazy(() => import('pages/ReceiveNotifsPage'));
+const NotifSettingsPage = lazy(() => import('pages/NotifSettingsPage'));
const SendNotifsPage = lazy(() => import('pages/SendNotifsPage'));
const SpacePage = lazy(() => import('pages/SpacePage'));
const SpamPage = lazy(() => import('pages/SpamPage'));
@@ -97,6 +98,7 @@ function MasterInterfacePage() {
} />
} />
} />
+ } />
} />
} />
diff --git a/yarn.lock b/yarn.lock
index b10c26aed1..e66c89183d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2166,7 +2166,7 @@ __metadata:
languageName: node
linkType: hard
-"@emotion/core@npm:^10.0.0, @emotion/core@npm:^10.0.15":
+"@emotion/core@npm:^10.0.0, @emotion/core@npm:^10.0.14, @emotion/core@npm:^10.0.15":
version: 10.3.1
resolution: "@emotion/core@npm:10.3.1"
dependencies:
@@ -5424,9 +5424,10 @@ __metadata:
react-dropzone-uploader: 2.11.0
react-easy-crop: ^4.1.4
react-ga: 2.7.0
- react-icons: ^4.7.1
+ react-icons: ^4.11.0
react-image-file-resizer: ^0.4.7
react-images-upload: ^1.2.8
+ react-input-slider: ^6.0.1
react-joyride: ^2.4.0
react-loader-spinner: ^5.3.4
react-multi-select-component: ^4.2.3
@@ -24812,12 +24813,12 @@ __metadata:
languageName: node
linkType: hard
-"react-icons@npm:^4.7.1":
- version: 4.8.0
- resolution: "react-icons@npm:4.8.0"
+"react-icons@npm:^4.11.0":
+ version: 4.11.0
+ resolution: "react-icons@npm:4.11.0"
peerDependencies:
react: "*"
- checksum: 4dbba7ad989c295b410e19b2a702722dae44368cb04b6515f9471353552f31ac80bd350f121d5bff79f81504b84039ede44d09e9f035f48bb1032e6eace126c4
+ checksum: 7b8b80bbe2dabcc54b6c2129b7761a04b19caebe24389adc7683478ef41212b9ca0b53c63abcc06b3c01b94c84855ec5142b4c357e19c4aaaad9a4db23a3c485
languageName: node
linkType: hard
@@ -24841,6 +24842,19 @@ __metadata:
languageName: node
linkType: hard
+"react-input-slider@npm:^6.0.1":
+ version: 6.0.1
+ resolution: "react-input-slider@npm:6.0.1"
+ dependencies:
+ "@babel/runtime": ^7.9.2
+ "@emotion/core": ^10.0.14
+ peerDependencies:
+ react: ">=16"
+ react-dom: ">=16"
+ checksum: 8611d1309bc8a10c7181f30c072840548f0911511d10b7c98928eef461297a9c9b645956cadbe0d8e586db4c958cb5e422fbe440d2b422e4cb79e841e4109ef6
+ languageName: node
+ linkType: hard
+
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.3, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.1, react-is@npm:^16.8.4":
version: 16.13.1
resolution: "react-is@npm:16.13.1"