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/ViewChannelItem.js b/src/components/ViewChannelItem.js
index e58d85bc37..f33ba75faa 100644
--- a/src/components/ViewChannelItem.js
+++ b/src/components/ViewChannelItem.js
@@ -1,5 +1,5 @@
// React + Web3 Essentials
-import React, { useEffect } from 'react';
+import React, { useEffect, useMemo } from 'react';
// External Packages
import Skeleton from '@yisheng90/react-loading';
@@ -11,6 +11,7 @@ import { toast as toaster } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css';
import styled, { css, useTheme } from 'styled-components';
import axios from 'axios';
+import { cloneDeep } from 'lodash';
// Internal Compoonents
import * as PushAPI from '@pushprotocol/restapi';
@@ -19,7 +20,7 @@ import MetaInfoDisplayer from 'components/MetaInfoDisplayer';
import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner';
import { convertAddressToAddrCaip } from 'helpers/CaipHelper';
import useToast from 'hooks/useToast';
-import { cacheChannelInfo, updateSubscriptionStatus } from 'redux/slices/channelSlice';
+import { cacheChannelInfo } from 'redux/slices/channelSlice';
import { addNewWelcomeNotif, incrementStepIndex } from 'redux/slices/userJourneySlice';
import ChannelTutorial, { isChannelTutorialized } from 'segments/ChannelTutorial';
import NotificationToast from '../primaries/NotificationToast';
@@ -35,6 +36,9 @@ import InfoImage from '../assets/info.svg';
import VerifiedTooltipContent from "./VerifiedTooltipContent";
import { IPFSGateway } from 'helpers/IpfsHelper';
import { useAccount, useDeviceWidthCheck } from 'hooks';
+import ManageNotifSettingDropdown from './dropdowns/ManageNotifSettingDropdown';
+import OptinNotifSettingDropdown from './dropdowns/OptinNotifSettingDropdown';
+import { ImageV2 } from './reusables/SharedStylingV2';
// Create Header
function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
@@ -48,7 +52,7 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
(state) => state.contracts
);
const { canVerify } = useSelector((state) => state.admin);
- const { channelsCache, CHANNEL_BLACKLIST, subscriptionStatus } = useSelector((state) => state.channels);
+ const { channelsCache, CHANNEL_BLACKLIST, subscriptionStatus, userSettings: currentUserSettings } = useSelector((state) => state.channels);
const { account, provider, chainId } = useAccount();
const onCoreNetwork = chainId === appConfig.coreContractChain;
@@ -183,11 +187,9 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
const generalToast = useToast();
- // to subscribe
- const subscribe = async () => {
- console.log('click executed');
- subscribeAction(false);
- };
+ const userSettings = useMemo(() => {
+ return cloneDeep(currentUserSettings);
+ }, [currentUserSettings]);
const formatAddress = (addressText) => {
return addressText.length > 40 ? `${shortenText(addressText, 4, 6)}` : addressText;
@@ -322,125 +324,6 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
});
};
- const subscribeToast = useToast();
- const subscribeAction = async () => {
- setTxInProgress(true);
- try {
- let channelAddress = channelObject.channel;
- if (!onCoreNetwork) {
- channelAddress = channelObject.alias_address;
- }
-
- subscribeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
-
- if (run) {
- const type = {
- Subscribe: [
- { name: 'channel', type: 'address' },
- { name: 'subscriber', type: 'address' },
- { name: 'action', type: 'string' },
- ],
- };
-
- const message = {
- channel: channelAddress,
- subscriber: account,
- action: 'Subscribe',
- };
-
- await provider.getSigner(account)._signTypedData(EPNS_DOMAIN, type, message);
-
- console.log('in run');
- subscribeToast.showMessageToast({
- toastTitle: 'Success',
- toastMessage: 'Successfully opted into channel !',
- toastType: 'SUCCESS',
- getToastIcon: (size) => (
-
- ),
- });
-
- dispatch(
- addNewWelcomeNotif({
- cta: '',
- title: channelObject.info,
- message: `Welcome to ${channelObject.name} Channel. From now onwards, you'll be getting notifications from this channel`,
- icon: channelIcon,
- url: channelObject.url,
- sid: '',
- app: channelObject.name,
- image: '',
- })
- );
- setTxInProgress(false);
- setSubscribed(true);
- if (stepIndex === 5) {
- console.log('this is working');
- dispatch(incrementStepIndex());
- }
- return;
- }
-
- const _signer = await provider.getSigner(account);
- await PushAPI.channels.subscribe({
- signer: _signer,
- channelAddress: convertAddressToAddrCaip(channelAddress, chainId), // channel address in CAIP
- userAddress: convertAddressToAddrCaip(account, chainId), // user address in CAIP
- onSuccess: () => {
- dispatch(updateSubscriptionStatus({ channelAddress: channelObject.channel, status: true }));
- setSubscribed(true);
- setSubscriberCount(subscriberCount + 1);
-
- subscribeToast.showMessageToast({
- toastTitle: 'Success',
- toastMessage: 'Successfully opted into channel !',
- toastType: 'SUCCESS',
- getToastIcon: (size) => (
-
- ),
- });
- },
- onError: () => {
- console.error('opt in error');
- subscribeToast.showMessageToast({
- toastTitle: 'Error',
- toastMessage: `There was an error opting into channel`,
- toastType: 'ERROR',
- getToastIcon: (size) => (
-
- ),
- });
- },
- env: appConfig.pushNodesEnv,
- });
- } catch (err) {
- subscribeToast.showMessageToast({
- toastTitle: 'Error',
- toastMessage: `There was an error opting into channel ( ${err.message} )`,
- toastType: 'ERROR',
- getToastIcon: (size) => (
-
- ),
- });
-
- console.log(err);
- } finally {
- setTxInProgress(false);
- }
- };
-
const copyToClipboard = (address) => {
let hostname = window.location.hostname;
// if we are on localhost, attach the port
@@ -461,73 +344,6 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
}
};
- const unsubscribeToast = useToast();
- const unsubscribeAction = async () => {
- try {
- let channelAddress = channelObject.channel;
- if (!onCoreNetwork) {
- channelAddress = channelObject.alias_address;
- }
-
- unsubscribeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
-
- const _signer = await provider.getSigner(account);
- await PushAPI.channels.unsubscribe({
- signer: _signer,
- channelAddress: convertAddressToAddrCaip(channelAddress, chainId), // channel address in CAIP
- userAddress: convertAddressToAddrCaip(account, chainId), // user address in CAIP
- onSuccess: () => {
- dispatch(updateSubscriptionStatus({ channelAddress: channelObject.channel, status: false }));
- setSubscribed(false);
- setSubscriberCount(subscriberCount - 1);
-
- unsubscribeToast.showMessageToast({
- toastTitle: 'Success',
- toastMessage: 'Successfully opted out of channel !',
- toastType: 'SUCCESS',
- getToastIcon: (size) => (
-
- ),
- });
- },
- onError: () => {
- console.error('opt out error');
- unsubscribeToast.showMessageToast({
- toastTitle: 'Error',
- toastMessage: `There was an error opting out of channel`,
- toastType: 'ERROR',
- getToastIcon: (size) => (
-
- ),
- });
- },
- env: appConfig.pushNodesEnv,
- });
- } catch (err) {
- unsubscribeToast.showMessageToast({
- toastTitle: 'Error',
- toastMessage: `There was an error opting out of channel ( ${err.message} )`,
- toastType: 'ERROR',
- getToastIcon: (size) => (
-
- ),
- });
-
- console.log(err);
- } finally {
- setTxInProgress(false);
- }
- };
-
const correctChannelTitleLink = () => {
const channelLink = CTA_OVERRIDE_CACHE[channelObject.channel] || channelObject.url;
if (/(?:http|https):\/\//i.test(channelLink)) {
@@ -1038,22 +854,31 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
<>
{isOwner && Owner}
{!isOwner && (
- {
+ setSubscribed(true);
+ setSubscriberCount((prevSubscriberCount) => prevSubscriberCount + 1)
+ }}
>
- {txInProgress && (
-
-
-
- )}
- Opt-In
-
+ {}}
+ disabled={txInProgress}
+ className="optin"
+ >
+ {txInProgress && (
+
+
+
+ )}
+ Opt-In
+
+
)}
>
)}
@@ -1061,21 +886,39 @@ function ViewChannelItem({ channelObjectProp, loadTeaser, playTeaser }) {
<>
{isOwner && Owner}
{!isOwner && (
- {
+ setSubscribed(false);
+ setSubscriberCount((prevSubscriberCount) => prevSubscriberCount - 1)
+ }}
>
- {txInProgress && (
-
-
-
- )}
- Opt-out
-
+ {}}
+ disabled={txInProgress}
+ >
+ {txInProgress && (
+
+
+
+ )}
+ Manage
+
+
+
)}
>
)}
@@ -1433,8 +1276,6 @@ const ChannelActionButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
- padding: 8px 15px;
- margin: 10px;
color: #fff;
border-radius: 5px;
font-size: 14px;
@@ -1503,11 +1344,9 @@ const SkeletonButton = styled.div`
const SubscribeButton = styled(ChannelActionButton)`
background: #e20880;
border-radius: 8px;
- padding: 9px 15px;
- min-width: 80px;
- @media (max-width: 768px) {
- padding: 9px 30px;
- }
+ padding: 0px;
+ min-height: 36px;
+ min-width: 108px;
`;
const UnsubscribeButton = styled(ChannelActionButton)`
@@ -1515,15 +1354,17 @@ const UnsubscribeButton = styled(ChannelActionButton)`
color: ${(props) => props.theme.viewChannelPrimaryText};
border: 1px solid #bac4d6;
border-radius: 8px;
- padding: 9px 15px;
- min-width: 80px;
- @media (max-width: 768px) {
- padding: 9px 30px;
- }
+ padding: 0px 8px 0px 16px;
+ gap: 8px;
+ min-height: 36px;
+ min-width: 108px;
`;
const OwnerButton = styled(ChannelActionButton)`
background: #35c5f3;
+ border-radius: 8px;
+ min-height: 36px;
+ min-width: 108px;
`;
const Toaster = styled.div`
diff --git a/src/components/channel/AddSettingModalContent.tsx b/src/components/channel/AddSettingModalContent.tsx
new file mode 100644
index 0000000000..987e69db2a
--- /dev/null
+++ b/src/components/channel/AddSettingModalContent.tsx
@@ -0,0 +1,375 @@
+// 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.type === 1 && settingToEdit.default) || (settingToEdit.type === 2 && settingToEdit.enabled)
+ : 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.type === 2 ? 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),
+ enabled: isDefault,
+ 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;
+ cursor: pointer;
+`;
+
+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..3b717ca92b
--- /dev/null
+++ b/src/components/channel/ChannelButtons.tsx
@@ -0,0 +1,90 @@
+// 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;
+}
+
+interface ModifySettingsButtonProps extends ChannelButtonProps {
+ title?: string;
+}
+
+export const AddDelegateButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Add Delegate
+
+ );
+};
+
+export const ManageSettingsButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Manage Settings
+
+ );
+};
+
+export const ModifySettingsButton = ({ onClick, title }: ModifySettingsButtonProps) => {
+ return (
+
+ {title ? title : 'Modify Settings'}
+
+ );
+};
+
+export const AddSettingButton = ({ onClick }: ChannelButtonProps) => {
+ return (
+
+
+ Add Setting
+
+ );
+};
+
+const ChannelButton = styled(Button)`
+ min-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`
+ min-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..cfc1a44841
--- /dev/null
+++ b/src/components/channel/ChannelInfoList.tsx
@@ -0,0 +1,160 @@
+// 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 DelegateSettingsDropdown, { ChannelDropdownOption } from './DelegateSettingsDropdown';
+import EmptyNotificationSettings from './EmptyNotificationSettings';
+import Tag from '../reusables/labels/Tag';
+
+// Internal Configs
+import { device } from 'config/Globals';
+import { ChannelSetting } from 'helpers/channel/types';
+
+// Types
+interface ChannelInfoListCommonProps {
+ isLoading: boolean;
+ account: string;
+ style?: CSSProperties;
+}
+
+interface AddressListOptions extends ChannelInfoListCommonProps {
+ isAddress: true;
+ items: string[];
+ addressDropdownOptions: Array;
+}
+
+interface SettingListOptions extends ChannelInfoListCommonProps {
+ isAddress: false;
+ items: Array;
+ isLoading: boolean;
+ settingsDropdownOptions?: Array;
+ onClickEmptyListButton: () => void;
+ emptyListButtonTitle: string;
+}
+
+type ChannelInfoListProps = AddressListOptions | SettingListOptions;
+
+const ChannelInfoList = (props: ChannelInfoListProps) => {
+ const isOwner = (account: string, delegate: string) => {
+ return account.toLowerCase() === delegate.toLowerCase();
+ };
+
+ 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 === false && (
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+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 NotificationSettingName = styled.span`
+ margin-left: 15px;
+ color: ${(props) =>
+ props.theme.scheme === 'light' ? props.theme.default.color : props.theme.default.secondaryColor};
+`;
+
+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/EmptyNotificationSettings.tsx b/src/components/channel/EmptyNotificationSettings.tsx
new file mode 100644
index 0000000000..637c115f0b
--- /dev/null
+++ b/src/components/channel/EmptyNotificationSettings.tsx
@@ -0,0 +1,76 @@
+// React + Web3 Essentials
+import React from 'react';
+
+// External Packages
+import styled from 'styled-components';
+
+// Internal Components
+import Icon from 'assets/navigation/receiveNotifOffIcon.svg';
+import { ImageV2 } from 'components/reusables/SharedStylingV2';
+import { ModifySettingsButton } from './ChannelButtons';
+
+// Types
+interface EmptyNotificationSettingsProps {
+ onClick: () => void;
+ title: string;
+ description: string;
+ buttonTitle?: string;
+ showTopBorder?: boolean;
+}
+
+const EmptyNotificationSettings = ({
+ description,
+ onClick,
+ title,
+ buttonTitle,
+ showTopBorder = true,
+}: EmptyNotificationSettingsProps) => {
+ return (
+
+
+ {title}
+ {description}
+
+
+ );
+};
+
+export default EmptyNotificationSettings;
+
+const EmptyNotificationSetting = styled.div<{ showTopBorder: boolean }>`
+ border-top: ${(props) => props.showTopBorder && `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;
+`;
diff --git a/src/components/channel/NotificationSettings.tsx b/src/components/channel/NotificationSettings.tsx
new file mode 100644
index 0000000000..4e0cc885b7
--- /dev/null
+++ b/src/components/channel/NotificationSettings.tsx
@@ -0,0 +1,282 @@
+// React + Web3 Essentials
+import React, { useEffect, useMemo } from 'react';
+import { ethers } from 'ethers';
+
+// External Packages
+import 'react-dropdown/style.css';
+import { useDispatch, 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';
+import { updateChannelSetting } from 'redux/slices/channelSlice';
+
+// 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 { channelSettings } = useSelector((state: any) => state.channels);
+
+ const dispatch = useDispatch();
+
+ 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 [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]);
+
+ 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 (channelAddress && channelSettings[channelAddress]) {
+ setSettings(channelSettings[channelAddress] || []);
+ setIsLoadingSettings(false);
+ }
+ }, [channelAddress, channelSettings]);
+
+ // 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 = settings.length;
+ 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.enabled ? '1' : '0'}-${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();
+ dispatch(updateChannelSetting({ channelAddress, settings }));
+ setIsLoading(false);
+
+ notificationToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: `Channel Settings Updated Successfully`,
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ } catch (err) {
+ setIsLoading(false);
+ 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);
+ }
+ }
+ };
+
+ const settingsChanged = useMemo(() => {
+ if (!settings || !channelSettings[account]) return false;
+ if (settings.length !== channelSettings[account].length) return true;
+ let isUnchanged = true;
+ for (let i = 0; i < settings.length; i++) {
+ const setting1 = settings[i];
+ const setting2 = channelSettings[account][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;
+ }
+ }
+ return isUnchanged === false;
+ }, [settings, channelSettings[account]]);
+
+ return (
+ <>
+ }
+ />
+ ,
+ onClick: editSetting,
+ text: 'Edit',
+ },
+ {
+ icon: ,
+ onClick: deleteSetting,
+ text: 'Delete',
+ },
+ ]}
+ />
+
+
+ >
+ );
+}
+
+// Export Default
+export default NotificationSettings;
diff --git a/src/components/channel/UserSettings.tsx b/src/components/channel/UserSettings.tsx
new file mode 100644
index 0000000000..666486a91d
--- /dev/null
+++ b/src/components/channel/UserSettings.tsx
@@ -0,0 +1,379 @@
+// React + Web3 Essentials
+import React, { useEffect, useMemo, useState } from 'react';
+
+// External Packages
+import styled from 'styled-components';
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { AiOutlineMore } from 'react-icons/ai';
+import { cloneDeep } from 'lodash';
+
+// Internal Components
+import { useAccount } from 'hooks';
+import { Button } from 'primaries/SharedStyling';
+import { ImageV2 } from 'components/reusables/SharedStylingV2';
+import { getChannel, getUserSubscriptions } from 'services';
+import LoaderSpinner from 'primaries/LoaderSpinner';
+import EmptyNotificationSettings from './EmptyNotificationSettings';
+import { updateBulkSubscriptions, updateBulkUserSettings } from 'redux/slices/channelSlice';
+import { convertAddressToAddrCaip } from 'helpers/CaipHelper';
+import ManageNotifSettingDropdown from 'components/dropdowns/ManageNotifSettingDropdown';
+
+// Internal Configs
+import { device } from 'config/Globals';
+
+interface ChannelListItem {
+ channel: string;
+ icon: string;
+ name: string;
+ id: number;
+ channel_settings: string;
+}
+
+function UserSettings() {
+ const { account, chainId } = useAccount();
+ const { subscriptionStatus, userSettings: currentUserSettings } = useSelector((state: any) => state.channels);
+ const [selectedOption, setSelectedOption] = useState(0);
+ const [channelList, setChannelList] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const navigate = useNavigate();
+
+ const dispatch = useDispatch();
+
+ const fetchChannelDetails = async (channel: string) => {
+ const details = await getChannel({ channel });
+ if (details) {
+ const updatedChannelItem: ChannelListItem = {
+ channel,
+ id: details.id,
+ icon: details.icon,
+ name: details.name,
+ channel_settings: details.channel_settings,
+ };
+ return updatedChannelItem;
+ } else return undefined;
+ };
+
+ const fillData = async (details: any) => {
+ const data = await Promise.all(
+ Object.keys(details).map(async (channel) => {
+ const channelData = await fetchChannelDetails(channel);
+ if (channelData) return channelData;
+ })
+ );
+ setChannelList(data);
+ };
+
+ useEffect(() => {
+ if (!account) return;
+ (async function () {
+ setIsLoading(true);
+ if (Object.keys(subscriptionStatus).length === 0) {
+ const userCaipAddress = convertAddressToAddrCaip(account, chainId);
+ const subscriptionsArr = await getUserSubscriptions({ userCaipAddress });
+ const subscriptionsMapping = {};
+ const userSettings = {};
+ subscriptionsArr.map(({ channel, user_settings }) => {
+ subscriptionsMapping[channel] = true;
+ userSettings[channel] = user_settings ? JSON.parse(user_settings) : null;
+ });
+ dispatch(updateBulkSubscriptions(subscriptionsMapping));
+ dispatch(updateBulkUserSettings(userSettings));
+ await fillData(subscriptionsMapping);
+ } else {
+ await fillData(subscriptionStatus);
+ }
+ setIsLoading(false);
+ })();
+ }, [account]);
+
+ const navigateToChannels = () => {
+ navigate('/channels');
+ };
+
+ const selectOptions = [
+ {
+ value: 0,
+ label: 'Notification Settings',
+ },
+ ];
+
+ const userSettings = useMemo(() => {
+ return cloneDeep(currentUserSettings);
+ }, [currentUserSettings]);
+
+ return (
+
+ Settings
+ Customize your Push profile or manage your notification preferences
+
+
+ {selectOptions.map((selectOptions) => (
+ setSelectedOption(selectOptions.value)}
+ key={selectOptions.value}
+ isSelected={selectOptions.value === selectedOption}
+ >
+ {selectOptions.label}
+
+ ))}
+
+
+
+ {selectOptions[selectedOption].label}
+ <>
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+ {channelList.length > 0 ? (
+ channelList.map((channel, index) => (
+ <>
+ {channel && (
+ <>
+
+
+
+ {channel.name}
+
+ {
+ setChannelList((prevChannelList) =>
+ prevChannelList.filter((item) => item?.id !== channel.id)
+ );
+ }}
+ >
+
+
+
+ {index !== channelList.length - 1 &&
}
+ >
+ )}
+ >
+ ))
+ ) : (
+
+
+
+ )}
+ >
+ )}
+ >
+
+
+
+
+ );
+}
+
+// Export Default
+export default UserSettings;
+
+const Container = styled.div`
+ padding: 32px 24px;
+ flex: 1;
+
+ @media ${device.tablet} {
+ padding: 24px 12px;
+ }
+`;
+
+const PageTitle = styled.div`
+ font-size: 32px;
+ font-weight: 500;
+ line-height: 45px;
+ letter-spacing: 0em;
+ text-align: left;
+ color: ${(props) => props.theme.default.color};
+
+ @media ${device.tablet} {
+ text-align: center;
+ margin-top: 24px;
+ }
+`;
+
+const PageDescription = styled.div`
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 21px;
+ letter-spacing: 0em;
+ text-align: left;
+ color: ${(props) => props.theme.default.secondaryColor};
+ margin-bottom: 40px;
+
+ @media ${device.tablet} {
+ text-align: center;
+ margin-bottom: 8px;
+ }
+`;
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ @media ${device.tablet} {
+ flex-direction: column;
+ }
+`;
+
+const SelectSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-right: 42px;
+
+ @media ${device.tablet} {
+ margin-right: 0px;
+ flex-direction: row;
+ overflow-x: scroll;
+ justify-content: center;
+ }
+`;
+
+const SelectListOption = styled(Button)<{ isSelected: boolean }>`
+ background-color: ${(props) => (props.isSelected ? props.theme.default.secondaryBg : 'transparent')};
+ color: ${(props) => props.theme.default.secondaryColor};
+ border-radius: 12px;
+ width: 100%;
+ padding: 14px;
+ margin: 10px 0px;
+ justify-content: flex-start;
+
+ &:hover:after {
+ background-color: ${(props) => props.theme.default.secondaryBg};
+ }
+
+ @media ${device.tablet} {
+ padding: 12px;
+ max-width: fit-content;
+ }
+`;
+
+const ChannelWrapper = styled.div`
+ border: 1px solid ${(props) => props.theme.default.borderColor};
+ padding: 12px;
+ border-radius: 16px;
+ flex-grow: 1;
+
+ @media ${device.tablet} {
+ margin: 8px 0px;
+ padding: 12px 6px;
+ }
+`;
+
+const ChannelContainer = styled.div`
+ overflow: hidden;
+ overflow-y: scroll;
+ height: 55vh;
+ padding: 12px;
+
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ position: absolute;
+ right: 10px;
+ }
+
+ &::-webkit-scrollbar {
+ background-color: transparent;
+ width: 4px;
+ position: absolute;
+ right: 10px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #d53a94;
+ border-radius: 99px;
+ width: 4px;
+ position: absolute;
+ right: 10px;
+ }
+
+ @media ${device.tablet} {
+ margin: 8px 0px;
+ padding: 12px 6px;
+ }
+`;
+
+const SectionTitle = styled.div`
+ font-size: 22px;
+ font-weight: 500;
+ line-height: 33px;
+ letter-spacing: -0.019em;
+ text-align: left;
+ margin-bottom: 20px;
+ color: ${(props) => props.theme.default.color};
+
+ @media ${device.tablet} {
+ text-align: center;
+ }
+`;
+
+const SettingsListItem = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ margin: 12px 0px;
+`;
+
+const Icon = styled(ImageV2)`
+ border: 1px solid ${(props) => props.theme.default.borderColor};
+ border-radius: 8px;
+ overflow: hidden;
+ margin-right: 16px;
+ width: 28px;
+ height: 28px;
+`;
+
+const ChannelName = styled.span`
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 23px;
+ letter-spacing: 0em;
+ color: ${(props) => props.theme.default.color};
+`;
+
+const SettingsListRow = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+`;
+
+const HR = styled.span`
+ background-color: ${(props) => props.theme.default.borderColor};
+ width: 100%;
+ display: flex;
+ height: 1px;
+`;
+
+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 CenterContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 80%;
+`;
diff --git a/src/components/dropdowns/DropdownBtnHandler.tsx b/src/components/dropdowns/DropdownBtnHandler.tsx
new file mode 100644
index 0000000000..0841041295
--- /dev/null
+++ b/src/components/dropdowns/DropdownBtnHandler.tsx
@@ -0,0 +1,77 @@
+import React, { useRef } from 'react';
+
+// External Packages
+import styled, { css } from 'styled-components';
+
+// Internal Configs
+import { useClickAway } from 'hooks/useClickAway';
+import { ItemHV2 } from 'components/reusables/SharedStylingV2';
+
+interface DropdownBtnHandlerProps {
+ children: React.ReactNode;
+ renderDropdownContainer: React.ReactNode;
+ showDropdown: boolean;
+ toggleDropdown: () => void;
+ closeDropdown: () => void;
+ containerPadding?: string;
+ centerOnMobile: boolean;
+}
+
+export const DropdownBtnHandler: React.FC = ({
+ children,
+ renderDropdownContainer,
+ showDropdown,
+ toggleDropdown,
+ closeDropdown,
+ containerPadding,
+ centerOnMobile,
+}) => {
+ const dropdownRef = useRef(null);
+ const renderDropdownContainerRef = useRef(null);
+
+ useClickAway(dropdownRef, renderDropdownContainerRef, closeDropdown);
+
+ return (
+
+ {children}
+ {showDropdown && (
+
+ e.stopPropagation()}>
+ {renderDropdownContainer}
+
+
+ )}
+
+ );
+};
+
+const Container = styled.button`
+ position:relative;
+ margin: 0;
+ padding: 0;
+ background: none;
+ border: 0;
+ outline: 0;
+`
+
+const DropdownContainer = styled(ItemHV2)<{ containerPadding?: string, centerOnMobile: boolean }>`
+ background: ${(props)=>props.theme.settingsModalBackground};
+ border:1px solid;
+ border-color:${(props)=>props.theme.settingsModalBorderColor};
+ border-radius: 8px;
+ box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.05);
+ align-items:flex-start;
+ padding: ${props => props.containerPadding ? props.containerPadding : '7px 14px'};
+ position:absolute;
+ top:0rem;
+ z-index:10;
+ right:-0.5rem;
+
+ @media (max-width:768px){
+ ${(props) => props.centerOnMobile && css`
+ left: 50%;
+ transform: translateX(-50%);
+ `}
+ width: fit-content;
+ }
+`;
diff --git a/src/components/dropdowns/ManageNotifSettingDropdown.tsx b/src/components/dropdowns/ManageNotifSettingDropdown.tsx
new file mode 100644
index 0000000000..00348ececb
--- /dev/null
+++ b/src/components/dropdowns/ManageNotifSettingDropdown.tsx
@@ -0,0 +1,251 @@
+// React + Web3 Essentials
+import React, { useContext, useMemo, useState } from "react";
+
+// External Packages
+import styled, { css, useTheme } from "styled-components";
+import { useDispatch } from "react-redux";
+
+// Internal Components
+import { DropdownBtnHandler } from "./DropdownBtnHandler";
+import UpdateNotifSettingDropdown from "./UpdateNotifSettingDropdown";
+
+// Internal Configs
+import { ImageV2, SpanV2 } from "components/reusables/SharedStylingV2";
+import { useAccount } from "hooks";
+import { AppContext } from "contexts/AppContext";
+import useToast from "hooks/useToast";
+import { appConfig } from "config";
+import { MdCheckCircle, MdError } from "react-icons/md";
+import LoaderSpinner, { LOADER_TYPE } from "components/reusables/loaders/LoaderSpinner";
+import { convertAddressToAddrCaip } from "helpers/CaipHelper";
+import { ChannelSetting, UserSetting } from "helpers/channel/types";
+import { removeUserSetting, updateSubscriptionStatus } from "redux/slices/channelSlice";
+
+interface ManageNotifSettingDropdownProps {
+ children: React.ReactNode;
+ centerOnMobile: boolean;
+ channelDetail: any;
+ userSetting?: UserSetting[];
+ onSuccessOptout: () => void;
+}
+
+interface ManageNotifSettingDropdownContainerProps {
+ centerOnMobile: boolean;
+ userSetting?: UserSetting[];
+ channelSetting?: ChannelSetting[];
+ channelDetail: any;
+ optOutHandler: (options: { setLoading?: React.Dispatch> }) => Promise;
+ closeDropdown: () => void;
+}
+
+const ManageNotifSettingDropdownContainer: React.FC = ({
+ centerOnMobile,
+ optOutHandler,
+ channelSetting,
+ channelDetail,
+ userSetting,
+ closeDropdown
+}) => {
+ const [txInProgress, setTxInProgress] = useState(false);
+
+ const theme = useTheme();
+
+ return (
+
+ {(channelSetting && channelSetting.length != 0) &&
+
+
+
+
+
+ Manage Settings
+
+
+
+
+}
+ optOutHandler({ setLoading: setTxInProgress })}>
+
+
+ {txInProgress &&
+
+ }
+ {!txInProgress && Opt-out}
+
+
+
+ );
+};
+
+const ManageNotifSettingDropdown: React.FC = (options) => {
+ const {
+ children,
+ centerOnMobile,
+ userSetting,
+ channelDetail,
+ onSuccessOptout
+ } = options;
+ const [isOpen, setIsOpen] = useState(false);
+ const { chainId } = useAccount();
+ const { userPushSDKInstance } = useContext(AppContext);
+ const dispatch = useDispatch();
+
+ const channelSetting = useMemo(() => {
+ if(channelDetail && channelDetail?.channel_settings) {
+ return JSON.parse(channelDetail?.channel_settings);
+ }
+ return null;
+ }, [channelDetail]);
+
+ const toggleDropdown = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const closeDropdown = () => {
+ setIsOpen(false);
+ };
+
+ const onCoreNetwork = chainId === appConfig.coreContractChain;
+
+ const unsubscribeToast = useToast();
+ const optOutHandler = async ({ setLoading }: { setLoading?: React.Dispatch> }) => {
+ const setLoadingFunc = setLoading || (() => {});
+ setLoadingFunc(true);
+
+ try {
+ let channelAddress = channelDetail.channel;
+ if (!onCoreNetwork) {
+ channelAddress = channelDetail.alias_address;
+ }
+
+ unsubscribeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
+
+ await userPushSDKInstance.notification.unsubscribe(convertAddressToAddrCaip(channelAddress, chainId), {
+ onSuccess: () => {
+ onSuccessOptout();
+ dispatch(updateSubscriptionStatus({ channelAddress: channelAddress, status: false }));
+ dispatch(removeUserSetting(channelAddress));
+
+ unsubscribeToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: 'Successfully opted out of channel !',
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+
+ closeDropdown();
+ },
+ onError: () => {
+ console.error('opt in error');
+ unsubscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error opting out of channel`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ },
+ });
+ } catch (err) {
+ unsubscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error opting into channel ( ${err.message} )`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+
+ console.log(err);
+ } finally {
+ setLoadingFunc(false);
+ }
+ };
+
+ // render
+ return (
+ }
+ containerPadding="12px 16px"
+ centerOnMobile={centerOnMobile}
+ >
+ {children}
+
+ );
+}
+
+// Export Default
+export default ManageNotifSettingDropdown;
+
+const DropdownOuterContainer = styled.div`
+ min-width: max-content;
+ gap: 16px;
+ display: flex;
+ flex-direction: column;
+`;
+
+const DropdownInnerContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const DropdownBtn = styled.button`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ background: transparent;
+ cursor: pointer;
+ gap: 8px;
+`;
+
+const ActionTitle = styled.span<{ hideIt: boolean }>`
+ ${(props) =>
+ props.hideIt &&
+ css`
+ visibility: hidden;
+ `};
+`;
\ No newline at end of file
diff --git a/src/components/dropdowns/OptinNotifSettingDropdown.tsx b/src/components/dropdowns/OptinNotifSettingDropdown.tsx
new file mode 100644
index 0000000000..e145c1d136
--- /dev/null
+++ b/src/components/dropdowns/OptinNotifSettingDropdown.tsx
@@ -0,0 +1,335 @@
+// React + Web3 Essentials
+import React, { useContext, useMemo, useState } from "react";
+
+// External Packages
+import Switch from 'react-switch';
+import Slider from 'react-input-slider';
+import styled, { css, useTheme } from "styled-components";
+import { useDispatch } from "react-redux";
+
+// Internal Components
+import { DropdownBtnHandler } from "./DropdownBtnHandler";
+
+// Internal Configs
+import { SpanV2 } from "components/reusables/SharedStylingV2";
+import { useAccount } from "hooks";
+import { appConfig } from "config";
+import { convertAddressToAddrCaip } from "helpers/CaipHelper";
+import useToast from "hooks/useToast";
+import { MdCheckCircle, MdError } from "react-icons/md";
+import { ChannelSetting } from "helpers/channel/types";
+import { notifChannelSettingFormatString, userSettingsFromDefaultChannelSetting } from "helpers/channel/notifSetting";
+import { AppContext } from "contexts/AppContext";
+import LoaderSpinner, { LOADER_TYPE } from "components/reusables/loaders/LoaderSpinner";
+import { updateSubscriptionStatus, updateUserSetting } from "redux/slices/channelSlice";
+
+interface OptinNotifSettingDropdownProps {
+ children: React.ReactNode;
+ channelDetail: any;
+ setLoading: (loading: boolean) => {};
+ onSuccessOptin: () => {};
+}
+
+interface OptinNotifSettingDropdownContainerProps {
+ settings: ChannelSetting[];
+ optInHandler: (options: { channelSettings?: ChannelSetting[], setLoading?: React.Dispatch> }) => Promise;
+}
+
+const OptinNotifSettingDropdownContainer: React.FC = ({ settings, optInHandler }) => {
+ const [modifiedSettings, setModifiedSettings] = useState([...settings]);
+ const [txInProgress, setTxInProgress] = useState(false);
+
+ const theme = useTheme();
+
+ const handleSliderChange = (index: number, value: number) => {
+ const updatedSettings = [...modifiedSettings];
+ updatedSettings[index].default = value;
+ setModifiedSettings(updatedSettings);
+ };
+
+ const handleSwitchChange = (index: number) => {
+ const updatedSettings = [...modifiedSettings];
+ if(updatedSettings[index].type === 1) {
+ // Type 1
+ // Use a type guard to narrow the type to ChannelSetting of type 1
+ const setting = updatedSettings[index] as ChannelSetting & { type: 1 };
+ setting.default = !setting.default;
+ } else {
+ // Type 2
+ // Use a type guard to narrow the type to ChannelSetting of type 2
+ const setting = updatedSettings[index] as ChannelSetting & { type: 2 };
+ setting.enabled = !setting.enabled;
+ }
+ setModifiedSettings(updatedSettings);
+ };
+
+ return (
+
+ {modifiedSettings.map((setting, index) => (
+
+
+ {setting.description}
+ handleSwitchChange(index)} checked={setting.type === 1 ? setting.default : setting.enabled}
+ checkedIcon={false}
+ uncheckedIcon={false}
+ onColor="#D53A94"
+ offColor="#A0A3B1"
+ height={16}
+ width={32}
+ handleDiameter={12}
+ />
+
+ {setting.type === 2 && setting.enabled === true && (
+
+ handleSliderChange(index, x)}
+ xstep={1}
+ xmin={setting.lowerLimit}
+ xmax={setting.upperLimit}
+ />
+ {setting.default}
+
+ )}
+
+ ))}
+
+ You will receive all important updates from this channel.
+ optInHandler({ channelSettings: modifiedSettings, setLoading: setTxInProgress })}
+ >
+ {txInProgress &&
+
+ }
+ {!txInProgress && Opt-in}
+
+
+
+ );
+};
+
+// Faucet URLs
+const OptinNotifSettingDropdown: React.FC = (options) => {
+ const { children, channelDetail, setLoading, onSuccessOptin } = options;
+
+ const { chainId } = useAccount();
+ const { userPushSDKInstance } = useContext(AppContext);
+ const [isOpen, setIsOpen] = useState(false);
+ const dispatch = useDispatch();
+
+ const onCoreNetwork = chainId === appConfig.coreContractChain;
+
+ const channelSetting = useMemo(() => {
+ if(channelDetail && channelDetail?.channel_settings) {
+ return JSON.parse(channelDetail?.channel_settings);
+ }
+ return null;
+ }, [channelDetail]);
+
+ const toggleDropdown = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const closeDropdown = () => {
+ setIsOpen(false);
+ };
+
+ const subscribeToast = useToast();
+ const optInHandler = async ({ channelSettings, setLoading }: { channelSettings?: ChannelSetting[], setLoading?: React.Dispatch> }) => {
+ const setLoadingFunc = setLoading || (options && options.setLoading) || (() => {});
+ setLoadingFunc(true);
+
+ try {
+ let channelAddress = channelDetail.channel;
+ if (!onCoreNetwork) {
+ channelAddress = channelDetail.alias_address;
+ }
+
+ subscribeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
+
+ await userPushSDKInstance.notification.subscribe(convertAddressToAddrCaip(channelAddress, chainId), {
+ settings: notifChannelSettingFormatString({ settings: channelSettings }),
+ // settings: [],
+ onSuccess: () => {
+ onSuccessOptin();
+ dispatch(updateSubscriptionStatus({ channelAddress, status: true }));
+ dispatch(updateUserSetting({ channelAddress, settings: userSettingsFromDefaultChannelSetting({ channelSetting: channelSettings })}));
+
+ subscribeToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: 'Successfully opted into channel !',
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ },
+ onError: () => {
+ console.error('opt in error');
+ subscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error opting into channel`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ },
+ });
+ } catch (err) {
+ subscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error opting into channel ( ${err.message} )`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+
+ console.log(err);
+ } finally {
+ setLoadingFunc(false);
+ }
+ };
+
+ // render
+ return (
+ (channelSetting && channelSetting.length) ?
+ }
+ containerPadding="0px 16px 16px 16px"
+ >
+ {children}
+
+ :
+
+ {children}
+
+
+ );
+}
+
+// Export Default
+export default OptinNotifSettingDropdown;
+
+const DropdownOuterContainer = styled.div`
+ min-width: 300px;
+`;
+
+const DropdownInnerContainer = styled.div<{ hasBottomBorder: boolean }>`
+ display: flex;
+ flex-direction: column;
+ min-width: 250px;
+
+ ${(props) =>
+ props.hasBottomBorder &&
+ css`
+ border-bottom: 1px solid ${(props) => props.theme.settingsModalBorderBottomColor};
+ `}
+`;
+
+const DropdownSwitchItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0px;
+`;
+
+const DropdownSubmitItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0px;
+`;
+
+const DropdownSubmitButton = styled.button`
+ border: 0;
+ outline: 0;
+ display: flex;
+ align-items: center;
+ min-width: 90px;
+ justify-content: center;
+ margin: 0px 0px 0px 10px;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 400;
+ position: relative;
+ background: #e20880;
+ border-radius: 8px;
+ padding: 9px 20px;
+ &:hover {
+ opacity: 0.9;
+ cursor: pointer;
+ pointer: hand;
+ }
+ &:active {
+ opacity: 0.75;
+ cursor: pointer;
+ pointer: hand;
+ }
+ ${(props) =>
+ props.disabled &&
+ css`
+ &:hover {
+ opacity: 1;
+ cursor: default;
+ pointer: default;
+ }
+ &:active {
+ opacity: 1;
+ cursor: default;
+ pointer: default;
+ }
+ `}
+`
+
+const DropdownSliderItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 10px;
+`;
+
+const ActionTitle = styled.span<{ hideIt: boolean }>`
+ ${(props) =>
+ props.hideIt &&
+ css`
+ visibility: hidden;
+ `};
+`;
diff --git a/src/components/dropdowns/UpdateNotifSettingDropdown.tsx b/src/components/dropdowns/UpdateNotifSettingDropdown.tsx
new file mode 100644
index 0000000000..18272d4f72
--- /dev/null
+++ b/src/components/dropdowns/UpdateNotifSettingDropdown.tsx
@@ -0,0 +1,335 @@
+// React + Web3 Essentials
+import React, { useContext, useState } from "react";
+
+// External Packages
+import Switch from 'react-switch';
+import Slider from 'react-input-slider';
+import styled, { css, useTheme } from "styled-components";
+import { useDispatch } from "react-redux";
+
+// Internal Components
+import { DropdownBtnHandler } from "./DropdownBtnHandler";
+
+// Internal Configs
+import { SpanV2 } from "components/reusables/SharedStylingV2";
+import useToast from "hooks/useToast";
+import { useAccount } from "hooks";
+import { AppContext } from "contexts/AppContext";
+import { appConfig } from "config";
+import { ChannelSetting, UserSetting } from "helpers/channel/types";
+import { convertAddressToAddrCaip } from "helpers/CaipHelper";
+import { notifUserSettingFormatString, userSettingsFromDefaultChannelSetting } from "helpers/channel/notifSetting";
+import { MdCheckCircle, MdError } from "react-icons/md";
+import LoaderSpinner, { LOADER_TYPE } from "components/reusables/loaders/LoaderSpinner";
+import { updateUserSetting } from "redux/slices/channelSlice";
+
+interface UpdateNotifSettingDropdownProps {
+ children: React.ReactNode;
+ centerOnMobile: boolean;
+ channelDetail: any;
+ channelSetting?: ChannelSetting[];
+ userSetting?: UserSetting[];
+ onSuccessSave?: () => void;
+}
+
+interface UpdateNotifSettingDropdownContainerProps {
+ settings: UserSetting[];
+ saveUserSettingHandler: (options: { userSettings?: UserSetting[], setLoading?: React.Dispatch> }) => Promise;
+}
+
+const UpdateNotifSettingDropdownContainer: React.FC = ({ settings, saveUserSettingHandler }) => {
+ const [modifiedSettings, setModifiedSettings] = useState([...settings]);
+ const [txInProgress, setTxInProgress] = useState(false);
+
+ const theme = useTheme();
+
+ const handleSliderChange = (index: number, value: number) => {
+ const updatedSettings = [...modifiedSettings];
+ updatedSettings[index].user = value;
+ setModifiedSettings(updatedSettings);
+ };
+
+ const handleSwitchChange = (index: number) => {
+ const updatedSettings = [...modifiedSettings];
+ if(updatedSettings[index].type === 1) {
+ // Type 1
+ // Use a type guard to narrow the type to UserSetting of type 1
+ const setting = updatedSettings[index] as UserSetting & { type: 1 };
+ setting.user = !setting.user;
+ } else {
+ // Type 2
+ // Use a type guard to narrow the type to UserSetting of type 2
+ const setting = updatedSettings[index] as UserSetting & { type: 2 };
+ setting.enabled = !setting.enabled;
+ }
+ setModifiedSettings(updatedSettings);
+ };
+
+ return (
+
+ {modifiedSettings.map((setting, index) => (
+
+
+ {setting.description}
+ handleSwitchChange(index)} checked={setting.type === 1 ? setting.user : setting.enabled}
+ checkedIcon={false}
+ uncheckedIcon={false}
+ onColor="#D53A94"
+ offColor="#A0A3B1"
+ height={16}
+ width={32}
+ handleDiameter={12}
+ />
+
+ {setting.type === 2 && setting.enabled === true && (
+
+ handleSliderChange(index, x)}
+ xstep={1}
+ xmin={setting.lowerLimit}
+ xmax={setting.upperLimit}
+ />
+ {setting.user}
+
+ )}
+
+ ))}
+
+ You will receive all important updates from this channel.
+ saveUserSettingHandler({ userSettings: modifiedSettings, setLoading: setTxInProgress })}
+ >
+ {txInProgress &&
+
+ }
+ {!txInProgress && Save}
+
+
+
+ );
+};
+
+// Faucet URLs
+const UpdateNotifSettingDropdown: React.FC = ({
+ children,
+ centerOnMobile,
+ channelDetail,
+ channelSetting,
+ userSetting,
+ onSuccessSave
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const { chainId } = useAccount();
+ const { userPushSDKInstance } = useContext(AppContext);
+ const dispatch = useDispatch();
+
+ const onCoreNetwork = chainId === appConfig.coreContractChain;
+
+ const toggleDropdown = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const closeDropdown = () => {
+ setIsOpen(false);
+ };
+
+ const subscribeToast = useToast();
+ const saveUserSettingHandler = async ({ userSettings, setLoading }: { userSettings?: UserSetting[], setLoading?: React.Dispatch> }) => {
+ const setLoadingFunc = setLoading || (() => {});
+ const saveOnSuccessSettingFunc = onSuccessSave || (() => {});
+ setLoadingFunc(true);
+
+ try {
+ let channelAddress = channelDetail.channel;
+ if (!onCoreNetwork) {
+ channelAddress = channelDetail.alias_address;
+ }
+
+ subscribeToast.showLoaderToast({ loaderMessage: 'Waiting for Confirmation...' });
+
+ await userPushSDKInstance.notification.subscribe(convertAddressToAddrCaip(channelAddress, chainId), {
+ settings: notifUserSettingFormatString({ settings: userSettings }),
+ // settings: [],
+ onSuccess: () => {
+ saveOnSuccessSettingFunc();
+ closeDropdown();
+ dispatch(updateUserSetting({ channelAddress, settings: userSetting }));
+
+ subscribeToast.showMessageToast({
+ toastTitle: 'Success',
+ toastMessage: 'Successfully saved the user settings!',
+ toastType: 'SUCCESS',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ },
+ onError: () => {
+ console.error('opt in error');
+ subscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error in saving the settings`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+ },
+ });
+ } catch (err) {
+ subscribeToast.showMessageToast({
+ toastTitle: 'Error',
+ toastMessage: `There was an error in saving the settings ( ${err.message} )`,
+ toastType: 'ERROR',
+ getToastIcon: (size) => (
+
+ ),
+ });
+
+ console.log(err);
+ } finally {
+ setLoadingFunc(false);
+ }
+ };
+
+ // render
+ return (
+ }
+ containerPadding="0px 16px 16px 16px"
+ >
+ {children}
+
+ );
+}
+
+// Export Default
+export default UpdateNotifSettingDropdown;
+
+const DropdownOuterContainer = styled.div`
+ min-width: 300px;
+`;
+
+const DropdownInnerContainer = styled.div<{ hasBottomBorder: boolean }>`
+ display: flex;
+ flex-direction: column;
+ min-width: 250px;
+
+ ${(props) =>
+ props.hasBottomBorder &&
+ css`
+ border-bottom: 1px solid ${(props) => props.theme.settingsModalBorderBottomColor};
+ `}
+`;
+
+const DropdownSwitchItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0px;
+`;
+
+const DropdownSubmitItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 0px;
+`;
+
+const DropdownSubmitButton = styled.button`
+ border: 0;
+ outline: 0;
+ display: flex;
+ align-items: center;
+ min-width: 90px;
+ justify-content: center;
+ margin: 0px 0px 0px 10px;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 400;
+ position: relative;
+ background: #e20880;
+ border-radius: 8px;
+ padding: 9px 20px;
+ &:hover {
+ opacity: 0.9;
+ cursor: pointer;
+ pointer: hand;
+ }
+ &:active {
+ opacity: 0.75;
+ cursor: pointer;
+ pointer: hand;
+ }
+ ${(props) =>
+ props.disabled &&
+ css`
+ &:hover {
+ opacity: 1;
+ cursor: default;
+ pointer: default;
+ }
+ &:active {
+ opacity: 1;
+ cursor: default;
+ pointer: default;
+ }
+ `}
+`
+
+const DropdownSliderItem = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 10px;
+`;
+
+const ActionTitle = styled.span<{ hideIt: boolean }>`
+ ${(props) =>
+ props.hideIt &&
+ css`
+ visibility: hidden;
+ `};
+`;
diff --git a/src/components/reusables/SharedStylingV2.js b/src/components/reusables/SharedStylingV2.js
index fb8af578fb..6e0dfb02e7 100644
--- a/src/components/reusables/SharedStylingV2.js
+++ b/src/components/reusables/SharedStylingV2.js
@@ -201,6 +201,8 @@ export const ImageV2 = styled.img`
export const AInlineV2 = styled.a`
background: transparent;
+ font-size: ${(props) => props.fontSize || 'inherit'};
+ font-weight: ${(props) => props.fontWeight || '300'};
color: ${(props) => props.color || '#e1087f'};
display: inline;
letter-spacing: inherit;
diff --git a/src/components/reusables/labels/Tag.tsx b/src/components/reusables/labels/Tag.tsx
new file mode 100644
index 0000000000..ad44e47f72
--- /dev/null
+++ b/src/components/reusables/labels/Tag.tsx
@@ -0,0 +1,21 @@
+// React + Web3 Essentials
+import React from 'react';
+
+// External Packages
+import styled from 'styled-components';
+
+const Tag = ({ children }) => {
+ return {children};
+};
+
+export default Tag;
+
+const TagLabel = 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;
+ max-height: 20px;
+`;
diff --git a/src/config/AppPaths.ts b/src/config/AppPaths.ts
new file mode 100644
index 0000000000..1302527e46
--- /dev/null
+++ b/src/config/AppPaths.ts
@@ -0,0 +1,26 @@
+// Define an enum for the paths
+enum APP_PATHS {
+ Inbox = '/inbox',
+ Spam = '/spam',
+ Chat = '/chat',
+ Spaces = '/spaces',
+ Channels = '/channels',
+ Dashboard = '/dashboard',
+ Send = '/send',
+ Receive = '/receive',
+ Govern = '/govern',
+ Snap = '/snap',
+ YieldV2 = '/yieldv2',
+ Rockstar = '/rockstar',
+ Gratitude = '/gratitude',
+ LiveWalkthrough = '/live_walkthrough',
+ ComingSoon = '/comingsoon',
+ NotAvailable = '/notavailable',
+ FAQ = '/faq',
+ Internal = '/internal',
+ Support = '/support',
+ UserSettings = '/user/settings',
+ ChannelSettings = '/channel/settings',
+}
+
+export default APP_PATHS;
diff --git a/src/config/NavigationList.js b/src/config/NavigationList.js
index 4d0df7653d..fd1b87abef 100644
--- a/src/config/NavigationList.js
+++ b/src/config/NavigationList.js
@@ -4,6 +4,7 @@ import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderS
// Internal Configs
import GLOBALS from 'config/Globals';
import { themeDark, themeLight } from 'config/Themization';
+import APP_PATHS from './AppPaths';
const NavigationList = {
primary: {
@@ -14,7 +15,7 @@ const NavigationList = {
name: 'Inbox',
title: 'Inbox',
alt: 'Open Inbox',
- href: '/inbox',
+ href: APP_PATHS.Inbox,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -39,7 +40,7 @@ const NavigationList = {
name: 'Channels',
title: 'Browse Channels',
alt: 'Open Channels',
- href: '/channels',
+ href: APP_PATHS.Channels,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -65,7 +66,7 @@ const NavigationList = {
name: 'Chat',
title: 'Chat',
alt: 'Open Chat',
- href: '/chat',
+ href: APP_PATHS.Chat,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -89,7 +90,7 @@ const NavigationList = {
name: 'Spaces',
title: 'Spaces',
alt: 'Open Spaces',
- href: '/spaces',
+ href: APP_PATHS.Spaces,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -116,7 +117,7 @@ const NavigationList = {
name: 'Create Channel',
title: 'Create Channel',
alt: 'Create Channels / Dashboard',
- href: '/dashboard',
+ href: APP_PATHS.Dashboard,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -142,7 +143,7 @@ const NavigationList = {
name: 'Send Notifications',
title: 'Send Notifications',
alt: 'Send Notifs',
- href: '/send',
+ href: APP_PATHS.Send,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -169,7 +170,7 @@ const NavigationList = {
name: 'Yield Farming V2',
title: 'Yield Farming V2',
alt: 'Open Yield Farming V2',
- href: '/yieldv2',
+ href: APP_PATHS.YieldV2,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -244,7 +245,7 @@ const NavigationList = {
name: 'Governance',
title: 'Governance',
alt: 'Governance',
- href: '/govern',
+ href: APP_PATHS.Govern,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -398,7 +399,7 @@ const NavigationList = {
name: 'FAQs',
title: 'Checkout Frequently Asked Questions',
alt: 'Open FAQs',
- href: '/faq',
+ href: APP_PATHS.FAQ,
newTab: false,
isRoute: true,
hasMenuLogic: true,
@@ -446,7 +447,7 @@ const NavigationList = {
name: 'Support',
title: 'Open a support ticket',
alt: 'Open Support Ticket',
- href: '/support',
+ href: APP_PATHS.Support,
newTab: false,
opened: false,
isRoute: true,
diff --git a/src/config/Themization.js b/src/config/Themization.js
index ca54836545..f49062faf2 100644
--- a/src/config/Themization.js
+++ b/src/config/Themization.js
@@ -11,6 +11,7 @@ const themeLight = {
secondaryColor: '#657795',
hover: '#F3F3FF',
borderColor: '#dfdee9',
+ primaryPushThemeTextColor: '#cf1c84'
},
// Login Theme
@@ -239,6 +240,14 @@ const themeLight = {
activeButtonText:'#657795',
stakingBorder:'#BAC4D6',
stakingEmptyButtonBG:'#DEDFE1',
+
+ // notif settings modal
+ settingsModalBorderBottomColor: '#D4DCEA',
+ sliderActiveColor: '#CF1C84',
+ sliderTrackColor: '#BAC4D6',
+ settingsModalPrimaryTextColor: '#1E1E1E',
+ settingsModalBorderColor: '#D4DFF2',
+ settingsModalBackground: '#FFF',
//spaces
spaceHostTextColor: '#1e1e1e',
@@ -268,6 +277,14 @@ const themeLight = {
snapBackground:'#F2F2F2',
snapBorderColor:'#BAC4D6',
+ // Notification Settings
+ nfsError: '#ED5858',
+ nfsDisabled: '#DFDEE9',
+ nfsDisabledText: '#AFB3BF',
+
+ // Send Notification
+ snfBorder: '#BAC4D6',
+ snfToggleBg: '#f4f5fa',
};
const themeDark = {
@@ -282,6 +299,7 @@ const themeDark = {
secondaryColor: '#B6BCD6',
hover: '#00000033',
borderColor: '#4A4F67',
+ primaryPushThemeTextColor: '#cf1c84'
},
// Login Theme
@@ -516,6 +534,14 @@ const themeDark = {
emptyButtonText:'#2D313C',
emptyButtonBg:'',
+ // notif settings modal
+ settingsModalBorderBottomColor: '#4A4F67',
+ sliderActiveColor: '#CF1C84',
+ sliderTrackColor: '#4A4F67',
+ settingsModalPrimaryTextColor: '#fff',
+ settingsModalBorderColor: '#4A4F67',
+ settingsModalBackground: '#2F3137',
+
//spaces
spaceHostTextColor: '#ffff',
@@ -542,8 +568,16 @@ const themeDark = {
snapPrimaryText:'#fff',
snapSecondaryText:'#B6BCD6',
snapBackground:'#404650',
- snapBorderColor:'#787E99'
+ snapBorderColor:'#787E99',
+ // Notification Settings
+ nfsError: '#ED5858',
+ nfsDisabled: '#AFB3BF',
+ nfsDisabledText: '#787E99',
+
+ // Send Notification
+ snfBorder: '#4A4F67',
+ snfToggleBg: '#404650',
};
module.exports = {
diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx
index cba96971b4..347afb1a74 100644
--- a/src/contexts/AppContext.tsx
+++ b/src/contexts/AppContext.tsx
@@ -1,14 +1,21 @@
// React + Web3 Essentials
import useModalBlur from "hooks/useModalBlur";
-import React,{createContext,useState} from "react"
+import React,{createContext,useEffect,useMemo,useState} from "react";
+
+// External Packages
+import { PushAPI } from "@pushprotocol/restapi";
// Internal Components
import { AppContextType, Web3NameListType } from "types/context"
+import { appConfig } from "config";
+import { useAccount } from "hooks";
export const AppContext = createContext(null);
const AppContextProvider=({children})=>{
const [web3NameList,setWeb3NameList]=useState({});
+ const [userPushSDKInstance, setUserPushSDKInstance] = useState(null);
+ const {account, provider} = useAccount();
const [SnapState, setSnapState] = useState(1);
const {
@@ -17,10 +24,31 @@ const AppContextProvider=({children})=>{
ModalComponent: MetamaskPushSnapModalComponent,
} = useModalBlur();
+ useEffect(() => {
+ const librarySigner = provider?.getSigner(account);
+ if(!account || !librarySigner || !appConfig?.appEnv) return;
+
+ const initializePushSDK = async () => {
+ try {
+ const userInstance = await PushAPI.initialize(librarySigner, {
+ env: appConfig.appEnv, // defaults to staging
+ account: account
+ });
+
+ setUserPushSDKInstance(userInstance);
+ } catch (error) {
+ // Handle initialization error
+ }
+ };
+
+ initializePushSDK();
+ }, [account, appConfig?.appEnv, provider]);
+
return(
{
+ 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;
+ }
+ }
+ }
+ return !hasError;
+};
diff --git a/src/helpers/channel/notifSetting.ts b/src/helpers/channel/notifSetting.ts
new file mode 100644
index 0000000000..8daba1d1c9
--- /dev/null
+++ b/src/helpers/channel/notifSetting.ts
@@ -0,0 +1,30 @@
+import { ChannelSetting, UserSetting } from "./types";
+
+const isSettingType1 = (setting: ChannelSetting) => setting.type === 1;
+
+export const notifChannelSettingFormatString = ({ settings }: { settings: ChannelSetting[] }) => {
+ let _notifSettings = [];
+ settings && settings.forEach((setting) =>
+ isSettingType1(setting)
+ ? _notifSettings.push({ enabled: setting.default })
+ : _notifSettings.push({ value: setting.default, enabled: (setting as ChannelSetting & { type: 2 }).enabled }));
+ return _notifSettings;
+}
+
+export const notifUserSettingFormatString = ({ settings }: { settings: UserSetting[] }) => {
+ let _notifSettings = [];
+ settings && settings.forEach((setting) =>
+ isSettingType1(setting)
+ ? _notifSettings.push({ enabled: setting.user })
+ : _notifSettings.push({ value: setting.user, enabled: (setting as ChannelSetting & { type: 2 }).enabled }));
+ return _notifSettings;
+}
+
+export const userSettingsFromDefaultChannelSetting = ({ channelSetting }: { channelSetting: ChannelSetting[] }) => {
+ let _userSettings = [];
+ channelSetting && channelSetting.forEach((setting) =>
+ isSettingType1(setting)
+ ? _userSettings.push({ ...setting, user: setting.default })
+ : _userSettings.push({ ...setting, user: setting.default }));
+ return _userSettings;
+};
\ No newline at end of file
diff --git a/src/helpers/channel/types.ts b/src/helpers/channel/types.ts
new file mode 100644
index 0000000000..e0f24954b1
--- /dev/null
+++ b/src/helpers/channel/types.ts
@@ -0,0 +1,36 @@
+export type ChannelSetting =
+| {
+ type: 1; // Boolean
+ default: boolean;
+ description: string;
+ index: number;
+ }
+| {
+ type: 2; // Range
+ default: number;
+ enabled: boolean;
+ description: string;
+ index: number;
+ lowerLimit: number;
+ upperLimit: number;
+ };
+
+export type UserSetting =
+| {
+ type: 1; // Boolean
+ default: boolean;
+ description: string;
+ index: number;
+ user: boolean;
+ }
+| {
+ type: 2; // Range
+ default: number;
+ enabled: boolean;
+ description: string;
+ index: number;
+ lowerLimit: number;
+ upperLimit: number;
+ user: 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/channels/ChannelsModule.tsx b/src/modules/channels/ChannelsModule.tsx
index 21c36bcce4..5b902d188e 100644
--- a/src/modules/channels/ChannelsModule.tsx
+++ b/src/modules/channels/ChannelsModule.tsx
@@ -10,10 +10,11 @@ import ViewChannels from "segments/ViewChannels";
// Internal Configs
import GLOBALS, { device, globalsMargin } from "config/Globals";
+import APP_PATHS from "config/AppPaths";
// Create Channels Module
const ChannelsModule = ({ loadTeaser, playTeaser }) => {
- ReactGA.pageview("/channels");
+ ReactGA.pageview(APP_PATHS.Channels);
// Render
return (
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/faq/FaqModule.tsx b/src/modules/faq/FaqModule.tsx
index 98136d2482..d84f3ba6f6 100644
--- a/src/modules/faq/FaqModule.tsx
+++ b/src/modules/faq/FaqModule.tsx
@@ -237,7 +237,7 @@ function FaqModule() {
Easiest way to create a channel is from our{' '}
-
+
Push (EPNS) Dapp
{' '}
itself. Find the entire channel creation process{' '}
diff --git a/src/modules/inbox/InboxModule.tsx b/src/modules/inbox/InboxModule.tsx
index a38666151c..3fa6a3c4ab 100644
--- a/src/modules/inbox/InboxModule.tsx
+++ b/src/modules/inbox/InboxModule.tsx
@@ -23,6 +23,7 @@ import { useAccount } from 'hooks';
// Internal Configs
import { abis, addresses, appConfig, CHAIN_DETAILS } from 'config';
import GLOBALS, { device, globalsMargin } from 'config/Globals';
+import APP_PATHS from 'config/AppPaths';
// Constants
export const ALLOWED_CORE_NETWORK = appConfig.coreContractChain;
@@ -30,7 +31,7 @@ export const ALLOWED_CORE_NETWORK = appConfig.coreContractChain;
// Create Inbox Module
const InboxModule = ({isSpam}) => {
// React GA Analytics
- ReactGA.pageview('/inbox');
+ ReactGA.pageview(APP_PATHS.Inbox)
const dispatch = useDispatch();
const { account, chainId, provider } = useAccount();
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/modules/userSettings/UserSettingsModule.tsx b/src/modules/userSettings/UserSettingsModule.tsx
new file mode 100644
index 0000000000..bfb1bcba4e
--- /dev/null
+++ b/src/modules/userSettings/UserSettingsModule.tsx
@@ -0,0 +1,58 @@
+// React + Web3 Essentials
+import React from 'react';
+
+// External Packages
+import styled from 'styled-components';
+
+// Internal Compoonents
+import UserSettings from 'components/channel/UserSettings';
+
+// Internal Configs
+import GLOBALS, { device, globalsMargin } from 'config/Globals';
+
+// Create Header
+const UserSettingsModule = () => {
+ return (
+
+
+
+ );
+}
+
+// css styles
+const Container = styled.div`
+ align-items: stretch;
+ align-self: stretch;
+ flex: 1;
+ background: ${(props) => props.theme.default.bg};
+ border-radius: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE};
+ box-shadow: ${GLOBALS.ADJUSTMENTS.MODULE_BOX_SHADOW};
+ display: flex;
+ flex-direction: column;
+ flex: initial;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ box-sizing: border-box;
+
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.DESKTOP};
+ height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.DESKTOP.TOP} - ${
+ globalsMargin.MINI_MODULES.DESKTOP.BOTTOM
+});
+
+ @media ${device.laptop} {
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.TABLET};
+ height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.TABLET.TOP} - ${
+ globalsMargin.MINI_MODULES.TABLET.BOTTOM
+ });
+ }
+
+ @media ${device.mobileL} {
+ margin: ${GLOBALS.ADJUSTMENTS.MARGIN.MINI_MODULES.MOBILE};
+ height: calc(100vh - ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px - ${globalsMargin.MINI_MODULES.MOBILE.TOP});
+ border: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE};
+ border-radius: ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} ${GLOBALS.ADJUSTMENTS.RADIUS.LARGE} 0 0;
+`;
+
+// Export Default
+export default UserSettingsModule;
diff --git a/src/pages/NotAvailablePage.tsx b/src/pages/NotAvailablePage.tsx
index 6b9ad9cdfc..7e8e1982a7 100644
--- a/src/pages/NotAvailablePage.tsx
+++ b/src/pages/NotAvailablePage.tsx
@@ -11,6 +11,7 @@ import { useAccount } from 'hooks';
// Internal Configs
import { appConfig } from "config";
+import APP_PATHS from "config/AppPaths";
function NotAvailablePage(props) {
const themes = useTheme();
@@ -20,7 +21,7 @@ function NotAvailablePage(props) {
React.useEffect(() => {
if (onCoreNetwork) {
const url = window.location.origin;
- window.location.replace(`${url}/#/channels`);
+ window.location.replace(`${url}${APP_PATHS.Channels}`);
}
})
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/pages/SpamPage.js b/src/pages/SpamPage.js
index 3765ceb60f..3b82ea5913 100644
--- a/src/pages/SpamPage.js
+++ b/src/pages/SpamPage.js
@@ -7,10 +7,11 @@ import styled from "styled-components";
// Internal Components
import Spambox from "segments/Spambox";
+import APP_PATHS from "config/AppPaths";
// Create Header
function InboxPage() {
- ReactGA.pageview("/spam");
+ ReactGA.pageview(APP_PATHS.Spam);
// Render
return (
diff --git a/src/pages/UserSettingsPage.tsx b/src/pages/UserSettingsPage.tsx
new file mode 100644
index 0000000000..435fa15143
--- /dev/null
+++ b/src/pages/UserSettingsPage.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 UserSettingsModule from 'modules/userSettings/UserSettingsModule';
+
+// Chat page
+const UserSettingsPage = () => {
+
+ // RENDER
+ return (
+
+
+
+ );
+}
+export default UserSettingsPage;
+
+// 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;
+`;
\ No newline at end of file
diff --git a/src/primaries/Profile.tsx b/src/primaries/Profile.tsx
index ff7f644265..05b4bcb557 100644
--- a/src/primaries/Profile.tsx
+++ b/src/primaries/Profile.tsx
@@ -12,10 +12,13 @@ import ProfileModal from 'components/ProfileModal';
import Dropdown from '../components/Dropdown';
import { useClickAway } from 'hooks/useClickAway';
import { useResolveWeb3Name } from 'hooks/useResolveWeb3Name';
+import { useAccount } from 'hooks';
+
+// Internal Configs
+import APP_PATHS from 'config/AppPaths';
import { AppContext } from 'contexts/AppContext';
import { ErrorContext } from 'contexts/ErrorContext';
import { AppContextType } from 'types/context';
-import { useAccount } from 'hooks';
// Create Header
const Profile = ({ isDarkMode }) => {
@@ -43,6 +46,14 @@ const Profile = ({ isDarkMode }) => {
function: ()=>{},
invertedIcon: './copy.svg',
},
+ {
+ id: 'userSettings',
+ value: '',
+ title: 'Settings',
+ function: ()=>{},
+ to: APP_PATHS.UserSettings,
+ invertedIcon: 'svg/setting.svg'
+ },
{
id: 'prodDapp',
value: '',
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/redux/slices/channelSlice.js b/src/redux/slices/channelSlice.js
index 232a4f0496..7356f5797c 100644
--- a/src/redux/slices/channelSlice.js
+++ b/src/redux/slices/channelSlice.js
@@ -14,6 +14,8 @@ const initialState = {
channels: [], // the channels meta-data
subscriptionStatus: {}, // a mapping of channel address to user's subscription status
channelsCache: {}, // a mapping of channel address to channel details
+ channelSettings: {}, // a mapping of channel address to channel settings
+ userSettings: {}, // a mapping of channel address to user settings
};
export const channelSlice = createSlice({
@@ -48,7 +50,24 @@ export const channelSlice = createSlice({
updateSubscriptionStatus: (state, action) => {
const { channelAddress, status } = action.payload;
state.subscriptionStatus[channelAddress] = status;
- }
+ },
+ updateBulkUserSettings: (state, action) => {
+ state.userSettings = action.payload;
+ },
+ updateUserSetting: (state, action) => {
+ const { channelAddress, settings } = action.payload;
+ state.userSettings[channelAddress] = settings;
+ },
+ removeUserSetting: (state, action) => {
+ delete state.userSettings[action.payload];
+ },
+ updateBulkChannelSettings: (state, action) => {
+ state.channelSettings = action.payload;
+ },
+ updateChannelSetting: (state, action) => {
+ const { channelAddress, settings } = action.payload;
+ state.channelSettings[channelAddress] = settings;
+ },
},
});
@@ -60,7 +79,12 @@ export const {
cacheSubscribe,
cacheUnsubscribe,
updateBulkSubscriptions,
- updateSubscriptionStatus
+ updateSubscriptionStatus,
+ updateBulkUserSettings,
+ updateUserSetting,
+ removeUserSetting,
+ updateBulkChannelSettings,
+ updateChannelSetting,
} = channelSlice.actions;
export default channelSlice.reducer;
diff --git a/src/segments/ViewChannels.tsx b/src/segments/ViewChannels.tsx
index 73ea1e2e08..5f293236fa 100644
--- a/src/segments/ViewChannels.tsx
+++ b/src/segments/ViewChannels.tsx
@@ -12,7 +12,7 @@ import Faucets from 'components/Faucets';
import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner';
import ViewChannelItem from 'components/ViewChannelItem';
import UtilityHelper, { MaskedAliasChannels, MaskedChannels } from 'helpers/UtilityHelper';
-import { incrementPage, setChannelMeta, updateBulkSubscriptions } from 'redux/slices/channelSlice';
+import { incrementPage, setChannelMeta, updateBulkSubscriptions, updateBulkUserSettings } from 'redux/slices/channelSlice';
import { incrementStepIndex } from 'redux/slices/userJourneySlice';
import DisplayNotice from '../primaries/DisplayNotice';
import { Item, ItemH } from '../primaries/SharedStyling';
@@ -196,8 +196,13 @@ function ViewChannels({ loadTeaser, playTeaser }) {
const userCaipAddress = convertAddressToAddrCaip(account, chainId);
const subscriptionsArr = await getUserSubscriptions({ userCaipAddress });
const subscriptionsMapping = {};
- subscriptionsArr.map(({ channel }) => (subscriptionsMapping[channel] = true));
+ const userSettings = {};
+ subscriptionsArr.map(({ channel, user_settings }) => {
+ subscriptionsMapping[channel] = true;
+ userSettings[channel] = user_settings ? JSON.parse(user_settings) : null;
+ });
dispatch(updateBulkSubscriptions(subscriptionsMapping));
+ dispatch(updateBulkUserSettings(userSettings));
})();
}, [account]);
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/Header.tsx b/src/structure/Header.tsx
index a7dd9c6ad8..5a829816c4 100644
--- a/src/structure/Header.tsx
+++ b/src/structure/Header.tsx
@@ -28,6 +28,34 @@ import MobileNavigation from './MobileNavigation';
import { useAccount, useDeviceWidthCheck } from 'hooks';
import ChainIndicator from 'components/ChainIndicator';
import { UnsupportedChainIdError } from 'connectors/error';
+import APP_PATHS from 'config/AppPaths';
+import { themeDark, themeLight } from 'config/Themization';
+
+// header tags for pages that are not there in navigationList (Sidebar)
+const EXTRA_HEADER_TAGS = {
+ [APP_PATHS.UserSettings]: {
+ title: 'Settings',
+ light: {
+ bg: GLOBALS.COLORS.GRADIENT_PRIMARY,
+ fg: themeLight.headerTagFg,
+ },
+ dark: {
+ bg: themeDark.headerTagBg,
+ fg: themeDark.headerTagFg,
+ },
+ },
+ [APP_PATHS.ChannelSettings]: {
+ title: 'Notification Settings',
+ light: {
+ bg: GLOBALS.COLORS.GRADIENT_PRIMARY,
+ fg: themeLight.headerTagFg,
+ },
+ dark: {
+ bg: themeDark.headerTagBg,
+ fg: themeDark.headerTagFg,
+ },
+ }
+}
// Create Header
function Header({ isDarkMode, darkModeToggle }) {
@@ -71,6 +99,9 @@ function Header({ isDarkMode, darkModeToggle }) {
const item = navigationSetup.navigation[key];
if (location.pathname === item.data.href) {
setHeaderTag(item.data.headerTag);
+ } else {
+ if(EXTRA_HEADER_TAGS[location.pathname])
+ setHeaderTag(EXTRA_HEADER_TAGS[location.pathname]);
}
});
}
diff --git a/src/structure/MasterInterfacePage.tsx b/src/structure/MasterInterfacePage.tsx
index b34e610825..f98c251d8f 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'));
@@ -30,6 +31,7 @@ const SupportPage = lazy(() => import('pages/SupportPage'));
const TutorialPage = lazy(() => import('pages/TutorialPage'));
// const YieldFarmingPage = lazy(() => import('pages/YieldFarmingPage'));
const YieldFarmingV2Page = lazy(() => import('pages/YieldFarmingPageV2'));
+const UserSettingsPage = lazy(() => import('pages/UserSettingsPage'));
// import AirdropPage from 'pages/AirdropPage';
// import ChannelDashboardPage from 'pages/ChannelDashboardPage';
@@ -61,6 +63,7 @@ import { AppContext } from 'contexts/AppContext';
import { AppContextType } from 'types/context';
import MetamaskPushSnapModal from 'modules/receiveNotifs/MetamaskPushSnapModal';
import { MODAL_POSITION } from 'hooks/useModalBlur';
+import APP_PATHS from 'config/AppPaths';
// Create Header
function MasterInterfacePage() {
@@ -82,36 +85,38 @@ function MasterInterfacePage() {
}
>
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
{/* } /> */}
}
/>
- } />
- } />
- } />
+ } />
+ } />
+ } />
- } />
- } />
+ } />
+ } />
{/* } /> */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
diff --git a/src/types/context.ts b/src/types/context.ts
index 32e044ac90..576bf0a74a 100644
--- a/src/types/context.ts
+++ b/src/types/context.ts
@@ -1,3 +1,5 @@
+import { PushAPI } from "@pushprotocol/restapi";
+
export interface Web3NameListType {
[key: string]: string;
}
@@ -5,6 +7,7 @@ export interface Web3NameListType {
export interface AppContextType {
web3NameList: Web3NameListType;
setWeb3NameList: (ens: Web3NameListType) => void;
+ userPushSDKInstance: PushAPI;
MetamaskPushSnapModalComponent:any,
showMetamaskPushSnap:any,
SnapState:number,
diff --git a/yarn.lock b/yarn.lock
index b10c26aed1..0423a7f5cf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -74,6 +74,13 @@ __metadata:
languageName: node
linkType: hard
+"@adraffy/ens-normalize@npm:1.9.4":
+ version: 1.9.4
+ resolution: "@adraffy/ens-normalize@npm:1.9.4"
+ checksum: 7d7fff58ebe2c4961f7e5e61dad123aa6a63fec0df5c84af1fa41079dc05d398599690be4427b3a94d2baa94084544bcfdf2d51cbed7504b9b0583b0960ad550
+ languageName: node
+ linkType: hard
+
"@ambire/signature-validator@npm:^1.3.1":
version: 1.3.1
resolution: "@ambire/signature-validator@npm:1.3.1"
@@ -1687,6 +1694,13 @@ __metadata:
languageName: node
linkType: hard
+"@bufbuild/protobuf@npm:^1.3.0":
+ version: 1.3.1
+ resolution: "@bufbuild/protobuf@npm:1.3.1"
+ checksum: 5ee96282a6259a222ccd0daf56bcab587918253750dda34c1bf2caad0d89bc3796970f7b55b1c7e25bae5d033c8e9dd687638675fe15cac7d9d6558ebfd4dfa8
+ languageName: node
+ linkType: hard
+
"@ceramicnetwork/3id-did-resolver@npm:^0.4.11":
version: 0.4.11
resolution: "@ceramicnetwork/3id-did-resolver@npm:0.4.11"
@@ -2166,7 +2180,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:
@@ -3663,6 +3677,15 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/core@npm:^1.4.2":
+ version: 1.5.0
+ resolution: "@floating-ui/core@npm:1.5.0"
+ dependencies:
+ "@floating-ui/utils": ^0.1.3
+ checksum: 54b4fe26b3c228746ac5589f97303abf158b80aa5f8b99027259decd68d1c2030c4c637648ebd33dfe78a4212699453bc2bd7537fd5a594d3bd3e63d362f666f
+ languageName: node
+ linkType: hard
+
"@floating-ui/dom@npm:^1.0.1":
version: 1.2.5
resolution: "@floating-ui/dom@npm:1.2.5"
@@ -3672,6 +3695,16 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/dom@npm:^1.1.0":
+ version: 1.5.3
+ resolution: "@floating-ui/dom@npm:1.5.3"
+ dependencies:
+ "@floating-ui/core": ^1.4.2
+ "@floating-ui/utils": ^0.1.3
+ checksum: 00053742064aac70957f0bd5c1542caafb3bfe9716588bfe1d409fef72a67ed5e60450d08eb492a77f78c22ed1ce4f7955873cc72bf9f9caf2b0f43ae3561c21
+ languageName: node
+ linkType: hard
+
"@floating-ui/dom@npm:^1.3.0":
version: 1.5.1
resolution: "@floating-ui/dom@npm:1.5.1"
@@ -3701,6 +3734,13 @@ __metadata:
languageName: node
linkType: hard
+"@floating-ui/utils@npm:^0.1.3":
+ version: 0.1.6
+ resolution: "@floating-ui/utils@npm:0.1.6"
+ checksum: b34d4b5470869727f52e312e08272edef985ba5a450a76de0917ba0a9c6f5df2bdbeb99448e2c60f39b177fb8981c772ff1831424e75123471a27ebd5b52c1eb
+ languageName: node
+ linkType: hard
+
"@fontsource/ibm-plex-mono@npm:^4.5.1":
version: 4.5.13
resolution: "@fontsource/ibm-plex-mono@npm:4.5.13"
@@ -4496,6 +4536,44 @@ __metadata:
languageName: node
linkType: hard
+"@livekit/components-core@npm:0.7.0":
+ version: 0.7.0
+ resolution: "@livekit/components-core@npm:0.7.0"
+ dependencies:
+ "@floating-ui/dom": ^1.1.0
+ email-regex: ^5.0.0
+ global-tld-list: ^0.0.1139
+ loglevel: ^1.8.1
+ rxjs: ^7.8.0
+ peerDependencies:
+ livekit-client: ^1.12.0
+ checksum: 8d225b6160197fa6fe74ec29226d0a58a4eca4e46978617f632a08a6295cbac49b5f1d8f06df89ea1e1476471ae3a141bb1719e142ac4686781ccf8adff7aa94
+ languageName: node
+ linkType: hard
+
+"@livekit/components-react@npm:^1.2.2":
+ version: 1.3.0
+ resolution: "@livekit/components-react@npm:1.3.0"
+ dependencies:
+ "@livekit/components-core": 0.7.0
+ "@react-hook/latest": ^1.0.3
+ clsx: ^2.0.0
+ usehooks-ts: ^2.9.1
+ peerDependencies:
+ livekit-client: ^1.12.0
+ react: ">=18"
+ react-dom: ">=18"
+ checksum: 1f71f688b6e5aaa4d221ffda67fceb0a9fbeeffb8b9fa9a282d7912ea5b786c1aef688811ad660acd5f18c8d7ec040be21dd28811988e6359acc7196c3fa047d
+ languageName: node
+ linkType: hard
+
+"@livekit/components-styles@npm:^1.0.6":
+ version: 1.0.6
+ resolution: "@livekit/components-styles@npm:1.0.6"
+ checksum: 5422458a2b442024b38b8b74313e417fd5feaa0b974e416eed45de4feb05b714175ba03ffb8f3cb04d8efd729f26f8f601d96d94d255805891124a84947f620e
+ languageName: node
+ linkType: hard
+
"@livepeer/core-react@npm:^1.8.0":
version: 1.8.0
resolution: "@livepeer/core-react@npm:1.8.0"
@@ -5104,7 +5182,7 @@ __metadata:
languageName: node
linkType: hard
-"@noble/curves@npm:^1.0.0":
+"@noble/curves@npm:1.2.0, @noble/curves@npm:^1.0.0, @noble/curves@npm:~1.2.0":
version: 1.2.0
resolution: "@noble/curves@npm:1.2.0"
dependencies:
@@ -5120,7 +5198,7 @@ __metadata:
languageName: node
linkType: hard
-"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.3.1":
+"@noble/hashes@npm:1.3.2, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.2":
version: 1.3.2
resolution: "@noble/hashes@npm:1.3.2"
checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474
@@ -5345,9 +5423,9 @@ __metadata:
"@mui/lab": ^5.0.0-alpha.72
"@mui/material": ^5.5.0
"@pushprotocol/ledgerlive": latest
- "@pushprotocol/restapi": 1.4.18
+ "@pushprotocol/restapi": 0.0.1-alpha.48
"@pushprotocol/socket": latest
- "@pushprotocol/uiweb": 1.1.13
+ "@pushprotocol/uiweb": 0.0.1-alpha.17
"@reduxjs/toolkit": ^1.7.1
"@testing-library/dom": ^6.12.2
"@testing-library/jest-dom": ^4.2.4
@@ -5424,9 +5502,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
@@ -5546,12 +5625,13 @@ __metadata:
languageName: node
linkType: hard
-"@pushprotocol/restapi@npm:1.4.18":
- version: 1.4.18
- resolution: "@pushprotocol/restapi@npm:1.4.18"
+"@pushprotocol/restapi@npm:0.0.1-alpha.48":
+ version: 0.0.1-alpha.48
+ resolution: "@pushprotocol/restapi@npm:0.0.1-alpha.48"
dependencies:
"@ambire/signature-validator": ^1.3.1
"@metamask/eth-sig-util": ^5.0.2
+ "@pushprotocol/socket": ^0.5.2
axios: ^0.27.2
buffer: ^6.0.3
crypto-js: ^4.1.1
@@ -5560,13 +5640,15 @@ __metadata:
livepeer: ^2.5.8
openpgp: ^5.5.0
simple-peer: ^9.11.1
+ socket.io-client: ^4.5.2
tslib: ^2.3.0
unique-names-generator: ^4.7.1
uuid: ^9.0.0
video-stream-merger: ^4.0.1
+ viem: ^1.3.0
peerDependencies:
ethers: ^5.6.8
- checksum: a350b385db72c5ce106ed940d77acfac11cec1960409b18698d63d3359a84788da503b62fa3fd01f94b7a7d3634614e26346432d833eb0d69b717c00bb627415
+ checksum: 49aa8e537574b4fbcefa154dbdde36c66bda285a306794dc259f6cbf7e056fdfe9e2d4a1bd3f4bfa0c0f25370b9687e083fabd0113c309e021a0b702ab42dd40
languageName: node
linkType: hard
@@ -5582,6 +5664,18 @@ __metadata:
languageName: node
linkType: hard
+"@pushprotocol/socket@npm:^0.5.2":
+ version: 0.5.2
+ resolution: "@pushprotocol/socket@npm:0.5.2"
+ dependencies:
+ socket.io-client: ^4.5.2
+ tslib: ^2.3.0
+ peerDependencies:
+ ethers: ^5.6.8
+ checksum: 14a438269eae87979e10377e5b8a38953e190593648ce64f148def553b66467cf52ec29ce2b70956342c23f42b67ae179f54a1aab6f22d1495a3806ba0146bbc
+ languageName: node
+ linkType: hard
+
"@pushprotocol/socket@npm:latest":
version: 0.4.2
resolution: "@pushprotocol/socket@npm:0.4.2"
@@ -5594,30 +5688,40 @@ __metadata:
languageName: node
linkType: hard
-"@pushprotocol/uiweb@npm:1.1.13":
- version: 1.1.13
- resolution: "@pushprotocol/uiweb@npm:1.1.13"
+"@pushprotocol/uiweb@npm:0.0.1-alpha.17":
+ version: 0.0.1-alpha.17
+ resolution: "@pushprotocol/uiweb@npm:0.0.1-alpha.17"
dependencies:
+ "@livekit/components-react": ^1.2.2
+ "@livekit/components-styles": ^1.0.6
"@livepeer/react": ^2.6.0
"@pushprotocol/socket": ^0.5.0
"@unstoppabledomains/resolution": ^8.5.0
+ "@web3-onboard/coinbase": ^2.2.5
+ "@web3-onboard/core": ^2.21.1
+ "@web3-onboard/injected-wallets": ^2.10.5
+ "@web3-onboard/react": ^2.8.9
+ "@web3-onboard/walletconnect": ^2.4.6
"@web3-react/injected-connector": ^6.0.7
date-fns: ^2.28.0
emoji-picker-react: ^4.4.9
+ ethers: ^5.6.8
font-awesome: ^4.7.0
gif-picker-react: ^1.1.0
html-react-parser: ^1.4.13
+ livekit-client: ^1.13.3
moment: ^2.29.4
react-icons: ^4.10.1
react-toastify: ^9.1.3
react-twitter-embed: ^4.0.4
+ uuid: ^9.0.1
peerDependencies:
"@pushprotocol/restapi": ^1.2.15
"@pushprotocol/socket": ^0.5.0
- ethers: ^5.7.1
+ axios: ^0.27.2
react: ">=16.8.0"
styled-components: ^5.3.5
- checksum: a03933a5e2c010435aad2bf36657198276b75025521146a64e66847f9f7f492ff85ec4612cf190758d6fac873634feb7250ccf2f3034f399859f478716a96c8e
+ checksum: 699b576cc1612b41e83790f3e9845fc7702ecf362fb542f28156dadec039f52e41962ab774b61a910c5c05a9afda4ce6d0b79e7d02d33240054f8df230bdad92
languageName: node
linkType: hard
@@ -6155,6 +6259,15 @@ __metadata:
languageName: node
linkType: hard
+"@react-hook/latest@npm:^1.0.3":
+ version: 1.0.3
+ resolution: "@react-hook/latest@npm:1.0.3"
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 2408c9cd35c5cfa7697b6da3bc5eebef254a932ade70955074c474f23be7dd3e2f81bbba12edcc9208bd0f89c6ed366d6b11d4f6d7b1052877a0bac8f74afad4
+ languageName: node
+ linkType: hard
+
"@react-spring/animated@npm:~9.7.1":
version: 9.7.1
resolution: "@react-spring/animated@npm:9.7.1"
@@ -6385,6 +6498,13 @@ __metadata:
languageName: node
linkType: hard
+"@scure/base@npm:~1.1.2":
+ version: 1.1.3
+ resolution: "@scure/base@npm:1.1.3"
+ checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c
+ languageName: node
+ linkType: hard
+
"@scure/bip32@npm:1.3.1":
version: 1.3.1
resolution: "@scure/bip32@npm:1.3.1"
@@ -6396,6 +6516,17 @@ __metadata:
languageName: node
linkType: hard
+"@scure/bip32@npm:1.3.2":
+ version: 1.3.2
+ resolution: "@scure/bip32@npm:1.3.2"
+ dependencies:
+ "@noble/curves": ~1.2.0
+ "@noble/hashes": ~1.3.2
+ "@scure/base": ~1.1.2
+ checksum: c5ae84fae43490853693b481531132b89e056d45c945fc8b92b9d032577f753dfd79c5a7bbcbf0a7f035951006ff0311b6cf7a389e26c9ec6335e42b20c53157
+ languageName: node
+ linkType: hard
+
"@scure/bip39@npm:1.2.1":
version: 1.2.1
resolution: "@scure/bip39@npm:1.2.1"
@@ -7868,6 +7999,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/ws@npm:^8.5.5":
+ version: 8.5.6
+ resolution: "@types/ws@npm:8.5.6"
+ dependencies:
+ "@types/node": "*"
+ checksum: 7addb0c5fa4e7713d5209afb8a90f1852b12c02cb537395adf7a05fbaf21205dc5f7c110fd5ad6f3dbf147112cbff33fb11d8633059cb344f0c14f595b1ea1fb
+ languageName: node
+ linkType: hard
+
"@types/yargs-parser@npm:*":
version: 21.0.0
resolution: "@types/yargs-parser@npm:21.0.0"
@@ -8556,6 +8696,30 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/core@npm:2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/core@npm:2.10.1"
+ dependencies:
+ "@walletconnect/heartbeat": 1.2.1
+ "@walletconnect/jsonrpc-provider": 1.0.13
+ "@walletconnect/jsonrpc-types": 1.0.3
+ "@walletconnect/jsonrpc-utils": 1.0.8
+ "@walletconnect/jsonrpc-ws-connection": 1.0.13
+ "@walletconnect/keyvaluestorage": ^1.0.2
+ "@walletconnect/logger": ^2.0.1
+ "@walletconnect/relay-api": ^1.0.9
+ "@walletconnect/relay-auth": ^1.0.4
+ "@walletconnect/safe-json": ^1.0.2
+ "@walletconnect/time": ^1.0.2
+ "@walletconnect/types": 2.10.1
+ "@walletconnect/utils": 2.10.1
+ events: ^3.3.0
+ lodash.isequal: 4.5.0
+ uint8arrays: ^3.1.0
+ checksum: d58ae15c53efe1792da8c7aa1b7ba47efb49807cfe0c73f225d59c5cd847a0e50979ce6965b94915812412deba3e5aa2dca13a02bd41c087e85575e99afad223
+ languageName: node
+ linkType: hard
+
"@walletconnect/core@npm:^1.8.0":
version: 1.8.0
resolution: "@walletconnect/core@npm:1.8.0"
@@ -8639,6 +8803,28 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/ethereum-provider@npm:^2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/ethereum-provider@npm:2.10.1"
+ dependencies:
+ "@walletconnect/jsonrpc-http-connection": ^1.0.7
+ "@walletconnect/jsonrpc-provider": ^1.0.13
+ "@walletconnect/jsonrpc-types": ^1.0.3
+ "@walletconnect/jsonrpc-utils": ^1.0.8
+ "@walletconnect/sign-client": 2.10.1
+ "@walletconnect/types": 2.10.1
+ "@walletconnect/universal-provider": 2.10.1
+ "@walletconnect/utils": 2.10.1
+ events: ^3.3.0
+ peerDependencies:
+ "@walletconnect/modal": ">=2"
+ peerDependenciesMeta:
+ "@walletconnect/modal":
+ optional: true
+ checksum: ec3d88ba101a5d8f193262b5b1e770cccad6457ec56fa1f3d17fa531de4e07e8cf03a1341669122c61956f0d5c3a6eca57d3f12f524e046acddb401cdb76fe7c
+ languageName: node
+ linkType: hard
+
"@walletconnect/events@npm:^1.0.1":
version: 1.0.1
resolution: "@walletconnect/events@npm:1.0.1"
@@ -8816,6 +9002,15 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/modal-core@npm:2.6.2":
+ version: 2.6.2
+ resolution: "@walletconnect/modal-core@npm:2.6.2"
+ dependencies:
+ valtio: 1.11.2
+ checksum: 94daceba50c323b06ecbeac2968d9f0972f327359c6118887c6526cd64006249b12f64322d71bc6c4a2b928436ecc89cf3d3af706511fcdc264c1f4b34a2dd5d
+ languageName: node
+ linkType: hard
+
"@walletconnect/modal-ui@npm:2.6.1":
version: 2.6.1
resolution: "@walletconnect/modal-ui@npm:2.6.1"
@@ -8828,6 +9023,18 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/modal-ui@npm:2.6.2":
+ version: 2.6.2
+ resolution: "@walletconnect/modal-ui@npm:2.6.2"
+ dependencies:
+ "@walletconnect/modal-core": 2.6.2
+ lit: 2.8.0
+ motion: 10.16.2
+ qrcode: 1.5.3
+ checksum: cd1ec0205eb491e529670599d3dd26f6782d7c5a99d5594bf6949a8c760c1c5f4eb6ed72b8662450774fe4e2dd47678f2c05145c8f2494bd7153446ddf4bd7ed
+ languageName: node
+ linkType: hard
+
"@walletconnect/modal@npm:2.6.1":
version: 2.6.1
resolution: "@walletconnect/modal@npm:2.6.1"
@@ -8838,6 +9045,16 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/modal@npm:2.6.2":
+ version: 2.6.2
+ resolution: "@walletconnect/modal@npm:2.6.2"
+ dependencies:
+ "@walletconnect/modal-core": 2.6.2
+ "@walletconnect/modal-ui": 2.6.2
+ checksum: 68b354d49960b96d22de0e47a3801df27c01a3e96ec5fbde3ca6df1344ca2b20668b0c4d58fe1803f5670ac7b7b4c6f5b7b405e354f5f9eaff5cca147c13de9c
+ languageName: node
+ linkType: hard
+
"@walletconnect/qrcode-modal@npm:^1.8.0":
version: 1.8.0
resolution: "@walletconnect/qrcode-modal@npm:1.8.0"
@@ -8921,6 +9138,23 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/sign-client@npm:2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/sign-client@npm:2.10.1"
+ dependencies:
+ "@walletconnect/core": 2.10.1
+ "@walletconnect/events": ^1.0.1
+ "@walletconnect/heartbeat": 1.2.1
+ "@walletconnect/jsonrpc-utils": 1.0.8
+ "@walletconnect/logger": ^2.0.1
+ "@walletconnect/time": ^1.0.2
+ "@walletconnect/types": 2.10.1
+ "@walletconnect/utils": 2.10.1
+ events: ^3.3.0
+ checksum: dbdced8dece73b20ae73df9c0cf0d9e3eee753f6c81e264c87583ca60d1d13d4f7d61944e4b22d1f70c5f32424fd842a7de778838aa7d0ae27195976a86e102f
+ languageName: node
+ linkType: hard
+
"@walletconnect/signer-connection@npm:^1.8.0":
version: 1.8.0
resolution: "@walletconnect/signer-connection@npm:1.8.0"
@@ -8969,6 +9203,20 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/types@npm:2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/types@npm:2.10.1"
+ dependencies:
+ "@walletconnect/events": ^1.0.1
+ "@walletconnect/heartbeat": 1.2.1
+ "@walletconnect/jsonrpc-types": 1.0.3
+ "@walletconnect/keyvaluestorage": ^1.0.2
+ "@walletconnect/logger": ^2.0.1
+ events: ^3.3.0
+ checksum: b663a236404bb423d3cc5cde656794ce42132f09193da5a51dac815d844f78eebb29c7275ebe10f6134492db21386ffd81b66ce42992332847b72c9128f74990
+ languageName: node
+ linkType: hard
+
"@walletconnect/types@npm:^1.8.0":
version: 1.8.0
resolution: "@walletconnect/types@npm:1.8.0"
@@ -8993,6 +9241,23 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/universal-provider@npm:2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/universal-provider@npm:2.10.1"
+ dependencies:
+ "@walletconnect/jsonrpc-http-connection": ^1.0.7
+ "@walletconnect/jsonrpc-provider": 1.0.13
+ "@walletconnect/jsonrpc-types": ^1.0.2
+ "@walletconnect/jsonrpc-utils": ^1.0.7
+ "@walletconnect/logger": ^2.0.1
+ "@walletconnect/sign-client": 2.10.1
+ "@walletconnect/types": 2.10.1
+ "@walletconnect/utils": 2.10.1
+ events: ^3.3.0
+ checksum: a33ad597a7601157cd96bceb7637c3463a5df981e5548c5343ab84f92c542bd7cae577fb2884d549164c9ad8262b097dc5fc0bc7fd9a515ee7c3f30b271cb034
+ languageName: node
+ linkType: hard
+
"@walletconnect/utils@npm:2.10.0":
version: 2.10.0
resolution: "@walletconnect/utils@npm:2.10.0"
@@ -9015,6 +9280,28 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/utils@npm:2.10.1":
+ version: 2.10.1
+ resolution: "@walletconnect/utils@npm:2.10.1"
+ dependencies:
+ "@stablelib/chacha20poly1305": 1.0.1
+ "@stablelib/hkdf": 1.0.1
+ "@stablelib/random": ^1.0.2
+ "@stablelib/sha256": 1.0.1
+ "@stablelib/x25519": ^1.0.3
+ "@walletconnect/relay-api": ^1.0.9
+ "@walletconnect/safe-json": ^1.0.2
+ "@walletconnect/time": ^1.0.2
+ "@walletconnect/types": 2.10.1
+ "@walletconnect/window-getters": ^1.0.1
+ "@walletconnect/window-metadata": ^1.0.1
+ detect-browser: 5.3.0
+ query-string: 7.1.3
+ uint8arrays: ^3.1.0
+ checksum: 150d1a3c75ce0736ffc8ed8a844e3dc63476e556f7f308154ee6bc9d99e08907bc11a504b7ce3889951293b48d9eef4e32b84de1c7f27b7a84e6731a7bb65189
+ languageName: node
+ linkType: hard
+
"@walletconnect/utils@npm:^1.8.0":
version: 1.8.0
resolution: "@walletconnect/utils@npm:1.8.0"
@@ -9107,6 +9394,38 @@ __metadata:
languageName: node
linkType: hard
+"@web3-onboard/core@npm:^2.21.1":
+ version: 2.21.2
+ resolution: "@web3-onboard/core@npm:2.21.2"
+ dependencies:
+ "@web3-onboard/common": ^2.3.3
+ bignumber.js: ^9.0.0
+ bnc-sdk: ^4.6.7
+ bowser: ^2.11.0
+ ethers: 5.5.3
+ eventemitter3: ^4.0.7
+ joi: 17.9.1
+ lodash.merge: ^4.6.2
+ lodash.partition: ^4.6.0
+ nanoid: ^4.0.0
+ rxjs: ^7.5.5
+ svelte: ^3.49.0
+ svelte-i18n: ^3.3.13
+ checksum: 13fa0df0c5c8b84cd65e363c2f48f1cd2bcb95217ce570faf87d0e6752add80365fc5554cfb706c375124fcd1e10a0e7321b297f330064574ffb8ac80d16d490
+ languageName: node
+ linkType: hard
+
+"@web3-onboard/injected-wallets@npm:^2.10.5":
+ version: 2.10.7
+ resolution: "@web3-onboard/injected-wallets@npm:2.10.7"
+ dependencies:
+ "@web3-onboard/common": ^2.3.3
+ joi: 17.9.1
+ lodash.uniqby: ^4.7.0
+ checksum: f74617456ec6a5eec45c5c484fc0e3b28d3c267657cb015baf2c53fd23fc1da550b6ce2f71964013b869de190e4bc8534e0753e01891a6dd83c9c3e1529d947a
+ languageName: node
+ linkType: hard
+
"@web3-onboard/injected-wallets@npm:^2.9.0":
version: 2.10.5
resolution: "@web3-onboard/injected-wallets@npm:2.10.5"
@@ -9147,6 +9466,22 @@ __metadata:
languageName: node
linkType: hard
+"@web3-onboard/walletconnect@npm:^2.4.6":
+ version: 2.4.7
+ resolution: "@web3-onboard/walletconnect@npm:2.4.7"
+ dependencies:
+ "@ethersproject/providers": 5.5.0
+ "@walletconnect/client": ^1.8.0
+ "@walletconnect/ethereum-provider": ^2.10.1
+ "@walletconnect/modal": 2.6.2
+ "@walletconnect/qrcode-modal": ^1.8.0
+ "@web3-onboard/common": ^2.3.3
+ joi: 17.9.1
+ rxjs: ^7.5.2
+ checksum: cfeefcf195ca84fac8de2f80ac8684e980d72c17d519185b2e817b218fa5274839d940b52d6866ae7c6b21b3570726950c84fdd4aee4e3af0a944e6223b1eacd
+ languageName: node
+ linkType: hard
+
"@web3-react/abstract-connector@npm:^6.0.7":
version: 6.0.7
resolution: "@web3-react/abstract-connector@npm:6.0.7"
@@ -9520,6 +9855,21 @@ __metadata:
languageName: node
linkType: hard
+"abitype@npm:0.9.8":
+ version: 0.9.8
+ resolution: "abitype@npm:0.9.8"
+ peerDependencies:
+ typescript: ">=5.0.4"
+ zod: ^3 >=3.19.1
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ zod:
+ optional: true
+ checksum: d7d887f29d6821e3f7a400de9620511b80ead3f85c5c87308aaec97965d3493e6687ed816e88722b4f512249bd66dee9e69231b49af0e1db8f69400a62c87cf6
+ languageName: node
+ linkType: hard
+
"abort-controller@npm:^3.0.0":
version: 3.0.0
resolution: "abort-controller@npm:3.0.0"
@@ -11922,6 +12272,13 @@ __metadata:
languageName: node
linkType: hard
+"clsx@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "clsx@npm:2.0.0"
+ checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e
+ languageName: node
+ linkType: hard
+
"co@npm:^4.6.0":
version: 4.6.0
resolution: "co@npm:4.6.0"
@@ -13878,6 +14235,13 @@ __metadata:
languageName: node
linkType: hard
+"email-regex@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "email-regex@npm:5.0.0"
+ checksum: 4089b601a0db88363391bb5c93f12eba56451687aadc925359b1408831b4b0f281737c51995a6fdbae5ff56f64b2fc2464189107575ecc0d67f0524f457a0632
+ languageName: node
+ linkType: hard
+
"emittery@npm:^0.10.2":
version: 0.10.2
resolution: "emittery@npm:0.10.2"
@@ -15309,7 +15673,7 @@ __metadata:
languageName: node
linkType: hard
-"ethers@npm:^5.3.1, ethers@npm:^5.6.5, ethers@npm:^5.7.2":
+"ethers@npm:^5.3.1, ethers@npm:^5.6.5, ethers@npm:^5.6.8, ethers@npm:^5.7.2":
version: 5.7.2
resolution: "ethers@npm:5.7.2"
dependencies:
@@ -16433,6 +16797,13 @@ __metadata:
languageName: node
linkType: hard
+"global-tld-list@npm:^0.0.1139":
+ version: 0.0.1139
+ resolution: "global-tld-list@npm:0.0.1139"
+ checksum: cfe5e6338059328e8b90ef890274419fb718e0c22f9aa0ec88dea016e1a9f756377ba102e1beffdc2d5c3fee3cf9e605cb76e056104f1d8602c599f447aa7d37
+ languageName: node
+ linkType: hard
+
"global@npm:~4.4.0":
version: 4.4.0
resolution: "global@npm:4.4.0"
@@ -18411,6 +18782,15 @@ __metadata:
languageName: node
linkType: hard
+"isomorphic-ws@npm:5.0.0":
+ version: 5.0.0
+ resolution: "isomorphic-ws@npm:5.0.0"
+ peerDependencies:
+ ws: "*"
+ checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
+ languageName: node
+ linkType: hard
+
"isomorphic-ws@npm:^4.0.1":
version: 4.0.1
resolution: "isomorphic-ws@npm:4.0.1"
@@ -20181,6 +20561,15 @@ __metadata:
languageName: node
linkType: hard
+"lit-html@npm:^2.8.0":
+ version: 2.8.0
+ resolution: "lit-html@npm:2.8.0"
+ dependencies:
+ "@types/trusted-types": ^2.0.2
+ checksum: 2d70df07248bcb2f502a3afb1e91d260735024fa669669ffb1417575aa39c3092779725ac1b90f5f39e4ce78c63f431f51176bc67f532389f0285a6991573255
+ languageName: node
+ linkType: hard
+
"lit@npm:2.7.6":
version: 2.7.6
resolution: "lit@npm:2.7.6"
@@ -20192,6 +20581,32 @@ __metadata:
languageName: node
linkType: hard
+"lit@npm:2.8.0":
+ version: 2.8.0
+ resolution: "lit@npm:2.8.0"
+ dependencies:
+ "@lit/reactive-element": ^1.6.0
+ lit-element: ^3.3.0
+ lit-html: ^2.8.0
+ checksum: 2480e733f7d022d3ecba91abc58a20968f0ca8f5fa30b3341ecf4bcf4845e674ad27b721a5ae53529cafc6ca603c015b80d0979ceb7a711e268ef20bb6bc7527
+ languageName: node
+ linkType: hard
+
+"livekit-client@npm:^1.13.3":
+ version: 1.13.4
+ resolution: "livekit-client@npm:1.13.4"
+ dependencies:
+ "@bufbuild/protobuf": ^1.3.0
+ events: ^3.3.0
+ loglevel: ^1.8.0
+ sdp-transform: ^2.14.1
+ ts-debounce: ^4.0.0
+ typed-emitter: ^2.1.0
+ webrtc-adapter: ^8.1.1
+ checksum: 90b54ad3dee69bac2f91d09c3db03928448432f8c27ca456717114c891543fab1e2796547322f9a7e3da6047973a0ba36de4b74ed4af66dba0ada25daac228b8
+ languageName: node
+ linkType: hard
+
"livepeer@npm:2.8.0, livepeer@npm:^2.5.8":
version: 2.8.0
resolution: "livepeer@npm:2.8.0"
@@ -20474,7 +20889,7 @@ __metadata:
languageName: node
linkType: hard
-"loglevel@npm:^1.7.0":
+"loglevel@npm:^1.7.0, loglevel@npm:^1.8.0, loglevel@npm:^1.8.1":
version: 1.8.1
resolution: "loglevel@npm:1.8.1"
checksum: a1a62db40291aaeaef2f612334c49e531bff71cc1d01a2acab689ab80d59e092f852ab164a5aedc1a752fdc46b7b162cb097d8a9eb2cf0b299511106c29af61d
@@ -24812,12 +25227,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 +25256,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"
@@ -26246,6 +26674,15 @@ __metadata:
languageName: node
linkType: hard
+"rxjs@npm:*, rxjs@npm:^7.5.5, rxjs@npm:^7.8.0":
+ version: 7.8.1
+ resolution: "rxjs@npm:7.8.1"
+ dependencies:
+ tslib: ^2.1.0
+ checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119
+ languageName: node
+ linkType: hard
+
"rxjs@npm:^6.6.3":
version: 6.6.7
resolution: "rxjs@npm:6.6.7"
@@ -26264,15 +26701,6 @@ __metadata:
languageName: node
linkType: hard
-"rxjs@npm:^7.5.5":
- version: 7.8.1
- resolution: "rxjs@npm:7.8.1"
- dependencies:
- tslib: ^2.1.0
- checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119
- languageName: node
- linkType: hard
-
"sade@npm:^1.8.1":
version: 1.8.1
resolution: "sade@npm:1.8.1"
@@ -26508,6 +26936,15 @@ __metadata:
languageName: node
linkType: hard
+"sdp-transform@npm:^2.14.1":
+ version: 2.14.1
+ resolution: "sdp-transform@npm:2.14.1"
+ bin:
+ sdp-verify: checker.js
+ checksum: 8b3179786db1a0f1ebfdacb1ac0dfe2833e63e8c64b638884cec212455061d53beaa8d9c8bf76fdbd5f844b7885f3892adec27e87734cfbc2b3e5c65e18a489b
+ languageName: node
+ linkType: hard
+
"sdp@npm:^2.12.0, sdp@npm:^2.6.0":
version: 2.12.0
resolution: "sdp@npm:2.12.0"
@@ -26515,6 +26952,13 @@ __metadata:
languageName: node
linkType: hard
+"sdp@npm:^3.2.0":
+ version: 3.2.0
+ resolution: "sdp@npm:3.2.0"
+ checksum: 227885bddab9a5845e56ae184ff51e43ec7bc155e7f1ed2f17ca1b012e6767011d5bd01b6c4064ded8e3b6f6bf3c9b26b2cf754b9c8662285988ed27b54f37b1
+ languageName: node
+ linkType: hard
+
"secp256k1-v4@https://github.com/HarshRajat/secp256k1-node":
version: 4.0.1
resolution: "secp256k1-v4@https://github.com/HarshRajat/secp256k1-node.git#commit=90a04a2e1127f4c1bfd7015aa5a7b22d08edb811"
@@ -28482,6 +28926,13 @@ __metadata:
languageName: node
linkType: hard
+"ts-debounce@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "ts-debounce@npm:4.0.0"
+ checksum: e1e509632c5aa09c40d3fa315b3a95b2c2e8813ccc706a400aa08e41f691e658061f34b42a1e8a578a043540d6db198e6ecf3ce26a5356a02a0940985fb1e379
+ languageName: node
+ linkType: hard
+
"ts-easing@npm:^0.2.0":
version: 0.2.0
resolution: "ts-easing@npm:0.2.0"
@@ -28725,6 +29176,18 @@ __metadata:
languageName: node
linkType: hard
+"typed-emitter@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "typed-emitter@npm:2.1.0"
+ dependencies:
+ rxjs: "*"
+ dependenciesMeta:
+ rxjs:
+ optional: true
+ checksum: 95821a9e05784b972cc9d152891fd12a56cb4b1a7c57e768c02bea6a8984da7aff8f19404a7b69eea11fae2a3b6c0c510a4c510f575f50162c759ae9059f2520
+ languageName: node
+ linkType: hard
+
"typedarray-to-buffer@npm:3.1.5, typedarray-to-buffer@npm:^3.1.5, typedarray-to-buffer@npm:~3.1.5":
version: 3.1.5
resolution: "typedarray-to-buffer@npm:3.1.5"
@@ -29118,6 +29581,16 @@ __metadata:
languageName: node
linkType: hard
+"usehooks-ts@npm:^2.9.1":
+ version: 2.9.1
+ resolution: "usehooks-ts@npm:2.9.1"
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ checksum: 36f1e4142ce23bc019b81d2e93aefd7f2c350abcf255598c21627114a69a2f2f116b35dc3a353375f09c6e4c9b704a04f104e3d10e98280545c097feca66c30a
+ languageName: node
+ linkType: hard
+
"utf-8-validate@npm:^5.0.2, utf-8-validate@npm:^5.0.8":
version: 5.0.10
resolution: "utf-8-validate@npm:5.0.10"
@@ -29224,6 +29697,15 @@ __metadata:
languageName: node
linkType: hard
+"uuid@npm:^9.0.1":
+ version: 9.0.1
+ resolution: "uuid@npm:9.0.1"
+ bin:
+ uuid: dist/bin/uuid
+ checksum: 39931f6da74e307f51c0fb463dc2462807531dc80760a9bff1e35af4316131b4fc3203d16da60ae33f07fdca5b56f3f1dd662da0c99fea9aaeab2004780cc5f4
+ languageName: node
+ linkType: hard
+
"v8-compile-cache@npm:^2.0.3":
version: 2.3.0
resolution: "v8-compile-cache@npm:2.3.0"
@@ -29257,6 +29739,24 @@ __metadata:
languageName: node
linkType: hard
+"valtio@npm:1.11.2":
+ version: 1.11.2
+ resolution: "valtio@npm:1.11.2"
+ dependencies:
+ proxy-compare: 2.5.1
+ use-sync-external-store: 1.2.0
+ peerDependencies:
+ "@types/react": ">=16.8"
+ react: ">=16.8"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ react:
+ optional: true
+ checksum: cce2d9212aac9fc4bdeba2d381188cc831cfe8d2d03039024cfcd58ba1801f2a5b14d01c2bb21a2c9f12046d2ede64f1dd887175185f39bee553677a35592c30
+ languageName: node
+ linkType: hard
+
"varint@npm:^5.0.0, varint@npm:^5.0.2, varint@npm:~5.0.0":
version: 5.0.2
resolution: "varint@npm:5.0.2"
@@ -29296,6 +29796,28 @@ __metadata:
languageName: node
linkType: hard
+"viem@npm:^1.3.0":
+ version: 1.15.4
+ resolution: "viem@npm:1.15.4"
+ dependencies:
+ "@adraffy/ens-normalize": 1.9.4
+ "@noble/curves": 1.2.0
+ "@noble/hashes": 1.3.2
+ "@scure/bip32": 1.3.2
+ "@scure/bip39": 1.2.1
+ "@types/ws": ^8.5.5
+ abitype: 0.9.8
+ isomorphic-ws: 5.0.0
+ ws: 8.13.0
+ peerDependencies:
+ typescript: ">=5.0.4"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 66fe2d8c00504e308b18d730dfd5bb7139e7b8a8c7fe10d8170c3bc4b33b25a9862dd09c7d4fac095310053a898dea18f850b72fcc5b408a439866c0e85d3f94
+ languageName: node
+ linkType: hard
+
"w3c-hr-time@npm:^1.0.2":
version: 1.0.2
resolution: "w3c-hr-time@npm:1.0.2"
@@ -29843,6 +30365,15 @@ __metadata:
languageName: node
linkType: hard
+"webrtc-adapter@npm:^8.1.1":
+ version: 8.2.3
+ resolution: "webrtc-adapter@npm:8.2.3"
+ dependencies:
+ sdp: ^3.2.0
+ checksum: 8239c9452c489c9aad2584b5d00af22462c3e0f1b7885c6e4036b518d2b9411d94c00d2ceadbed987459a3647cfc4ce04c0eb75dd5ae7c3d7df9b810525e6a07
+ languageName: node
+ linkType: hard
+
"websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4":
version: 0.7.4
resolution: "websocket-driver@npm:0.7.4"
@@ -30335,6 +30866,21 @@ __metadata:
languageName: node
linkType: hard
+"ws@npm:8.13.0, ws@npm:^8.13.0":
+ version: 8.13.0
+ resolution: "ws@npm:8.13.0"
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ">=5.0.2"
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c
+ languageName: node
+ linkType: hard
+
"ws@npm:^3.0.0":
version: 3.3.3
resolution: "ws@npm:3.3.3"
@@ -30361,21 +30907,6 @@ __metadata:
languageName: node
linkType: hard
-"ws@npm:^8.13.0":
- version: 8.13.0
- resolution: "ws@npm:8.13.0"
- peerDependencies:
- bufferutil: ^4.0.1
- utf-8-validate: ">=5.0.2"
- peerDependenciesMeta:
- bufferutil:
- optional: true
- utf-8-validate:
- optional: true
- checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c
- languageName: node
- linkType: hard
-
"ws@npm:^8.5.0":
version: 8.14.2
resolution: "ws@npm:8.14.2"