diff --git a/packages/demoreact/src/app/NotificationsTest.tsx b/packages/demoreact/src/app/NotificationsTest.tsx index 26eef90ec..5cb12858f 100644 --- a/packages/demoreact/src/app/NotificationsTest.tsx +++ b/packages/demoreact/src/app/NotificationsTest.tsx @@ -111,7 +111,7 @@ const NotificationsTest = () => { }); setSpams(spams); - + } catch (e) { console.error(e); } finally { @@ -146,12 +146,12 @@ const NotificationsTest = () => {

Notifications Test page

{/* */} - + {theme === 'dark' ? : } - + { setViewType('notif') }}>Notifications { setViewType('spam') }}>Spam @@ -170,8 +170,8 @@ const NotificationsTest = () => { {notifs ? ( {notifs.map((oneNotification, i) => { - - const { + + const { cta, title, message, @@ -215,7 +215,7 @@ const NotificationsTest = () => { {spams ? ( {spams.map((oneNotification, i) => { - const { + const { cta, title, message, @@ -259,4 +259,4 @@ const NotificationsTest = () => { ); } -export default NotificationsTest; \ No newline at end of file +export default NotificationsTest; diff --git a/packages/demoreact/src/app/SpaceUITest/SpaceFeed.tsx b/packages/demoreact/src/app/SpaceUITest/SpaceFeed.tsx index 5b204b591..0e27bc11f 100644 --- a/packages/demoreact/src/app/SpaceUITest/SpaceFeed.tsx +++ b/packages/demoreact/src/app/SpaceUITest/SpaceFeed.tsx @@ -1,6 +1,7 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { useSpaceComponents } from './useSpaceComponents'; import { Checkbox } from '../components/Checkbox'; +import { AccountContext } from '../context'; export const SpaceFeed = () => { const { SpaceFeedComponent } = useSpaceComponents(); @@ -10,6 +11,7 @@ export const SpaceFeed = () => { const [width, setWidth] = useState(); const [height, setHeight] = useState(500); const [sortingOrder, setSortingOrder] = useState([]); + const { setSpaceId } = useContext(AccountContext); const handleShowTab = () => { setShowTab(!showTab); @@ -78,6 +80,7 @@ export const SpaceFeed = () => { height={height} onBannerClickHandler={(spaceId: string) => { console.log('spaceId: ', spaceId); + setSpaceId(spaceId); }} /> diff --git a/packages/demoreact/src/app/SpaceUITest/SpaceWidget.tsx b/packages/demoreact/src/app/SpaceUITest/SpaceWidget.tsx index 1bb487857..1aed3664d 100644 --- a/packages/demoreact/src/app/SpaceUITest/SpaceWidget.tsx +++ b/packages/demoreact/src/app/SpaceUITest/SpaceWidget.tsx @@ -139,11 +139,14 @@ export const SpaceWidget = () => { - { - const { spaceUI } = useSpaceComponents(); const customtheme = { - statusColorError: 'red', - } + titleBg: 'linear-gradient(45deg, #E165EC 0.01%, #A483ED 100%)', //not changed + titleTextColor: '#FFFFFF', + bgColorPrimary: '#fff', + bgColorSecondary: '#F7F1FB', + textColorPrimary: '#000', + textColorSecondary: '#657795', + textGradient: 'linear-gradient(45deg, #B6A0F5, #F46EF6, #FFDED3, #FFCFC5)', //not changed + btnColorPrimary: '#D53A94', + btnOutline: '#D53A94', + borderColor: '#FFFF', + borderRadius: '17px', + containerBorderRadius: '12px', + statusColorError: '#E93636', + statusColorSuccess: '#30CC8B', + iconColorPrimary: '#82828A', + }; + + const customDarkTheme = { + titleBg: + 'linear-gradient(87.17deg, #EA4EE4 0%, #D23CDF 0.01%, #8B5CF6 100%)', + titleTextColor: '#fff', + bgColorPrimary: '#000', + bgColorSecondary: '#292344', + textColorPrimary: '#fff', + textColorSecondary: '#71717A', + textGradient: 'linear-gradient(45deg, #B6A0F5, #F46EF6, #FFDED3, #FFCFC5)', + btnColorPrimary: '#8B5CF6', + btnOutline: '#8B5CF6', + borderColor: '#3F3F46', + borderRadius: '17px', + containerBorderRadius: '12px', + statusColorError: '#E93636', + statusColorSuccess: '#30CC8B', + iconColorPrimary: '#71717A', + }; return ( - - {children} - + // + // {children} + // + <> ); }; diff --git a/packages/demoreact/src/app/SpaceUITest/useSpaceComponents.tsx b/packages/demoreact/src/app/SpaceUITest/useSpaceComponents.tsx index 5c0ee9ffc..0452683ea 100644 --- a/packages/demoreact/src/app/SpaceUITest/useSpaceComponents.tsx +++ b/packages/demoreact/src/app/SpaceUITest/useSpaceComponents.tsx @@ -6,12 +6,10 @@ import { SpacesUI, ISpaceInvitesProps, } from '@pushprotocol/uiweb'; -import React, { useContext, useEffect, useState } from 'react'; -import { EnvContext, Web3Context } from '../context'; -import * as PushAPI from '@pushprotocol/restapi'; +import React, { useContext } from 'react'; +import { AccountContext, EnvContext, Web3Context } from '../context'; export interface IUseSpaceReturnValues { - spaceUI: SpacesUI; SpaceInvitesComponent: React.FC; SpaceWidgetComponent: React.FC; SpaceFeedComponent: React.FC; @@ -22,39 +20,17 @@ export interface IUseSpaceReturnValues { export const useSpaceComponents = (): IUseSpaceReturnValues => { const { account, library } = useContext(Web3Context); const { env } = useContext(EnvContext); + const { pgpPrivateKey } = useContext(AccountContext); const librarySigner = library?.getSigner(); - const [pgpPrivateKey, setPgpPrivateKey] = useState(''); - const spaceUI = new SpacesUI({ - account: account, + account: account as string, signer: librarySigner, pgpPrivateKey: pgpPrivateKey, env: env, }); - useEffect(() => { - (async () => { - if (!account || !env || !library) return; - - const user = await PushAPI.user.get({ account, env }); - let pgpPrivateKey; - const librarySigner = await library.getSigner(account); - if (user?.encryptedPrivateKey) { - pgpPrivateKey = await PushAPI.chat.decryptPGPKey({ - encryptedPGPPrivateKey: user.encryptedPrivateKey, - account, - signer: librarySigner, - env, - }); - } - - setPgpPrivateKey(pgpPrivateKey); - })(); - }, [account, env, library]); - return { - spaceUI, SpaceInvitesComponent: spaceUI.SpaceInvites, SpaceWidgetComponent: spaceUI.SpaceWidget, SpaceBannerComponent: spaceUI.SpaceBanner, diff --git a/packages/demoreact/src/app/app.tsx b/packages/demoreact/src/app/app.tsx index e5a51911f..279a49e41 100644 --- a/packages/demoreact/src/app/app.tsx +++ b/packages/demoreact/src/app/app.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Route, Routes, Link } from 'react-router-dom'; import { useWeb3React } from '@web3-react/core'; import ConnectButton from './components/Connect'; import { Checkbox } from './components/Checkbox'; import Dropdown from './components/Dropdown'; -import { Web3Context, EnvContext, SocketContext } from './context'; +import { Web3Context, EnvContext, SocketContext, AccountContext } from './context'; import { useSDKSocket } from './hooks'; import { ReactComponent as PushLogo } from '../assets/pushLogo.svg'; import NotificationsTest from './NotificationsTest'; @@ -55,7 +55,6 @@ import GetSpacesRequestsTest from './SpaceTest/GetSpacesRequestsTest'; import GetSpacesTrendingTest from './SpaceTest/GetSpacesTrendingTest'; import SpaceUITest from './SpaceUITest/SpaceUITest'; import { - SpacesComponentProvider, SpaceWidget, SpaceBanner, SpaceFeed, @@ -63,7 +62,9 @@ import { SpaceInvitesComponent } from './SpaceUITest'; import { useSpaceComponents } from './SpaceUITest/useSpaceComponents'; +import * as PushAPI from "@pushprotocol/restapi"; import { ChatWidgetTest } from './ChatWidgetTest'; +import { SpacesUI, SpacesUIProvider } from '@pushprotocol/uiweb'; window.Buffer = window.Buffer || Buffer; @@ -147,6 +148,43 @@ const NavMenu = styled.div` } `; +const customtheme = { + titleBg: 'linear-gradient(45deg, #E165EC 0.01%, #A483ED 100%)', //not changed + titleTextColor: '#FFFFFF', + bgColorPrimary: '#fff', + bgColorSecondary: '#F7F1FB', + textColorPrimary: '#000', + textColorSecondary: '#657795', + textGradient: 'linear-gradient(45deg, #B6A0F5, #F46EF6, #FFDED3, #FFCFC5)', //not changed + btnColorPrimary: '#D53A94', + btnOutline: '#D53A94', + borderColor: '#FFFF', + borderRadius: '17px', + containerBorderRadius: '12px', + statusColorError: '#E93636', + statusColorSuccess: '#30CC8B', + iconColorPrimary: '#82828A', +}; + +const customDarkTheme = { + titleBg: + 'linear-gradient(87.17deg, #EA4EE4 0%, #D23CDF 0.01%, #8B5CF6 100%)', + titleTextColor: '#fff', + bgColorPrimary: '#000', + bgColorSecondary: '#292344', + textColorPrimary: '#fff', + textColorSecondary: '#71717A', + textGradient: 'linear-gradient(45deg, #B6A0F5, #F46EF6, #FFDED3, #FFCFC5)', + btnColorPrimary: '#8B5CF6', + btnOutline: '#8B5CF6', + borderColor: '#3F3F46', + borderRadius: '17px', + containerBorderRadius: '12px', + statusColorError: '#E93636', + statusColorSuccess: '#30CC8B', + iconColorPrimary: '#71717A', +}; + const checkForWeb3Data = ({ library, active, @@ -157,17 +195,18 @@ const checkForWeb3Data = ({ }; export function App() { - const web3Data: Web3ReactState = useWeb3React(); + const {account, library, active, chainId} = useWeb3React(); - const [env, setEnv] = useState(ENV.PROD); + const [env, setEnv] = useState(ENV.DEV); const [isCAIP, setIsCAIP] = useState(false); const { SpaceWidgetComponent } = useSpaceComponents(); const [spaceId, setSpaceId] = useState(''); + const [pgpPrivateKey, setPgpPrivateKey] = useState(''); const socketData = useSDKSocket({ - account: web3Data.account, - chainId: web3Data.chainId, + account: account, + chainId: chainId, env, isCAIP, }); @@ -180,6 +219,33 @@ export function App() { setIsCAIP(!isCAIP); }; + useEffect(() => { + (async () => { + if (!account || !env || !library) return; + + const user = await PushAPI.user.get({ account: account, env }); + let pgpPrivateKey; + const librarySigner = await library.getSigner(account); + if (user?.encryptedPrivateKey) { + pgpPrivateKey = await PushAPI.chat.decryptPGPKey({ + encryptedPGPPrivateKey: user.encryptedPrivateKey, + account: account, + signer: librarySigner, + env, + }); + } + + setPgpPrivateKey(pgpPrivateKey); + })(); + }, [account, env, library]); + + const spaceUI = useMemo(() => new SpacesUI({ + account: account as string, + signer: library?.getSigner(), + pgpPrivateKey: pgpPrivateKey, + env: env, + }), [account, library, pgpPrivateKey, env]); + return ( @@ -211,10 +277,13 @@ export function App() {
- {checkForWeb3Data(web3Data) ? ( - + {checkForWeb3Data({ + active, account, library, chainId + }) ? ( + - + + */} - + + ) : null} diff --git a/packages/demoreact/src/app/context/accountContext.ts b/packages/demoreact/src/app/context/accountContext.ts new file mode 100644 index 000000000..45750805a --- /dev/null +++ b/packages/demoreact/src/app/context/accountContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react' + +const AccountContext = createContext({}); + +export default AccountContext; \ No newline at end of file diff --git a/packages/demoreact/src/app/context/index.ts b/packages/demoreact/src/app/context/index.ts index cccf3958c..80db31f5d 100644 --- a/packages/demoreact/src/app/context/index.ts +++ b/packages/demoreact/src/app/context/index.ts @@ -1,9 +1,11 @@ import Web3Context from "./web3context"; import EnvContext from "./envContext"; import SocketContext from "./socketContext"; +import AccountContext from "./accountContext"; export { Web3Context, EnvContext, - SocketContext + SocketContext, + AccountContext }; \ No newline at end of file diff --git a/packages/examples/sdk-backend-node/src/spaces/index.ts b/packages/examples/sdk-backend-node/src/spaces/index.ts index f320f7659..c5c504073 100644 --- a/packages/examples/sdk-backend-node/src/spaces/index.ts +++ b/packages/examples/sdk-backend-node/src/spaces/index.ts @@ -55,7 +55,7 @@ export const runSpacesUseCases = async (): Promise < void > => { ╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ╚════██║ ███████║██║ ██║ ██║╚██████╗███████╗███████║ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚══════╝ - + `) console.log('PushAPI.user.create'); @@ -621,4 +621,4 @@ async function PushAPI_space_trending(silent = !showAPIResponse) { if (!silent) { console.log(response); } -} \ No newline at end of file +} diff --git a/packages/examples/sdk-frontend/automate.sh b/packages/examples/sdk-frontend/automate.sh new file mode 100755 index 000000000..5d58755d8 --- /dev/null +++ b/packages/examples/sdk-frontend/automate.sh @@ -0,0 +1,31 @@ +#! /bin/bash + +who=$(whoami) +echo "Hey, $who!" + +sleep 1 + +delnode=$(rm -rf node_modules/) +sleep 1 +echo "Node Modules Deleted." + +sleep 1 + +delnext=$(rm -rf .next/) +sleep 1 +echo ".Next Deleted" + +sleep 1 + +clean=$(yarn cache clean) +sleep 1 +echo "Yarn Cache Cleaned." + +sleep 1 +install=`yarn` +sleep 1 +echo $install + + +sleep 1 +echo "See you soon, $who!" diff --git a/packages/examples/sdk-frontend/components/Spaces/useSpaceComponent.tsx b/packages/examples/sdk-frontend/components/Spaces/useSpaceComponent.tsx index ccad60268..8797bff28 100644 --- a/packages/examples/sdk-frontend/components/Spaces/useSpaceComponent.tsx +++ b/packages/examples/sdk-frontend/components/Spaces/useSpaceComponent.tsx @@ -6,11 +6,11 @@ import { SpacesUI, ISpaceInvitesProps, } from '@pushprotocol/uiweb'; -import { useAccount, useNetwork, useSigner } from 'wagmi'; -import React, { useContext, useEffect, useState } from 'react'; +import { useAccount, useSigner } from 'wagmi'; +import React, { useContext} from 'react'; import { ENV } from '@pushprotocol/restapi/src/lib/constants'; import * as PushAPI from '@pushprotocol/restapi'; -import { is } from 'date-fns/locale'; +import { AccountContext } from '../../contexts'; export interface IUseSpaceReturnValues { spaceUI: SpacesUI; @@ -24,13 +24,10 @@ export interface IUseSpaceReturnValues { export const useSpaceComponents = (): IUseSpaceReturnValues => { const env = ENV.DEV; - const { address, isConnected } = useAccount(); - const { chain } = useNetwork(); + const { address } = useAccount(); const { data: signer } = useSigner(); - const [pgpPrivateKey, setPgpPrivateKey] = useState(''); - - console.log('address: ', address, isConnected); + const { pgpPrivateKey } = useContext(AccountContext); const spaceUI = new SpacesUI({ account: address as string, @@ -39,28 +36,6 @@ export const useSpaceComponents = (): IUseSpaceReturnValues => { env: env, }); - useEffect(() => { - (async () => { - if (!signer || !address || !chain?.id) return; - - const user = await PushAPI.user.get({ - account: address, - env, - }); - let pgpPrivateKey = null; - if (user?.encryptedPrivateKey) { - pgpPrivateKey = await PushAPI.chat.decryptPGPKey({ - encryptedPGPPrivateKey: user.encryptedPrivateKey, - account: address, - signer, - env, - }); - } - - setPgpPrivateKey(pgpPrivateKey); - })(); - }, [address, env, signer, chain]); - return { spaceUI, SpaceInvitesComponent: spaceUI.SpaceInvites, diff --git a/packages/examples/sdk-frontend/contexts/accountContext.ts b/packages/examples/sdk-frontend/contexts/accountContext.ts new file mode 100644 index 000000000..45750805a --- /dev/null +++ b/packages/examples/sdk-frontend/contexts/accountContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react' + +const AccountContext = createContext({}); + +export default AccountContext; \ No newline at end of file diff --git a/packages/examples/sdk-frontend/contexts/index.ts b/packages/examples/sdk-frontend/contexts/index.ts new file mode 100644 index 000000000..c5064de89 --- /dev/null +++ b/packages/examples/sdk-frontend/contexts/index.ts @@ -0,0 +1,5 @@ +import AccountContext from "./accountContext"; + +export { + AccountContext +}; \ No newline at end of file diff --git a/packages/examples/sdk-frontend/pages/_app.tsx b/packages/examples/sdk-frontend/pages/_app.tsx index 6013004e0..d99c4c625 100644 --- a/packages/examples/sdk-frontend/pages/_app.tsx +++ b/packages/examples/sdk-frontend/pages/_app.tsx @@ -12,7 +12,9 @@ import { publicProvider } from 'wagmi/providers/public'; import '@rainbow-me/rainbowkit/styles.css'; import '../styles/globals.css'; import { useEffect, useState } from 'react'; -import { SpacesComponentProvider } from './spaces'; +import { SpacesUIProvider } from '@pushprotocol/uiweb'; +import { useSpaceComponents } from './../components/Spaces/useSpaceComponent'; +import { AccountContext } from '../contexts'; const { chains, provider } = configureChains([goerli], [publicProvider()]); @@ -28,8 +30,27 @@ const wagmiClient = createClient({ provider, }); +export interface ISpacesComponentProps { + children: React.ReactNode; +} + +const SpacesComponentProvider = ({ children }: ISpacesComponentProps) => { + const { spaceUI } = useSpaceComponents(); + + const customtheme = { + statusColorError: 'red', + }; + + return ( + + {children} + + ); +}; + function MyApp({ Component, pageProps }: AppProps) { const [loadWagmi, setLoadWagmi] = useState(false); + const [pgpPrivateKey, setPgpPrivateKey] = useState(''); useEffect(() => { setLoadWagmi(true); @@ -41,9 +62,11 @@ function MyApp({ Component, pageProps }: AppProps) { {loadWagmi ? ( - - - + + + + + ) : null} diff --git a/packages/examples/sdk-frontend/pages/spaces/index.tsx b/packages/examples/sdk-frontend/pages/spaces/index.tsx index 651988b8d..80cf3ae9c 100644 --- a/packages/examples/sdk-frontend/pages/spaces/index.tsx +++ b/packages/examples/sdk-frontend/pages/spaces/index.tsx @@ -3,34 +3,45 @@ import styled from 'styled-components'; import { Button, Container } from '..'; import Link from 'next/link'; -import { SpacesUIProvider } from '@pushprotocol/uiweb'; import { useSpaceComponents } from './../../components/Spaces/useSpaceComponent'; +import { useContext, useEffect, useState } from 'react'; +import { useAccount, useSigner } from 'wagmi'; +import * as PushAPI from '@pushprotocol/restapi'; +import { ENV } from '@pushprotocol/restapi/src/lib/constants'; +import { AccountContext } from '../../contexts'; +const Spaces: NextPage = () => { + const { address } = useAccount(); + const { data: signer } = useSigner(); -export interface ISpacesComponentProps { - children: React.ReactNode; -} + const { pgpPrivateKey, setPgpPrivateKey } = useContext(AccountContext); + const { SpaceWidgetComponent } = useSpaceComponents(); + const env = ENV.DEV; -export const SpacesComponentProvider = ({ - children, -}: ISpacesComponentProps) => { - const { spaceUI } = useSpaceComponents(); + useEffect(() => { + (async () => { + if (!signer || !address || pgpPrivateKey) return; - const customtheme = { - statusColorError: 'red', - }; + const user = await PushAPI.user.get({ + account: address, + env, + }); + let PgpPrivateKey = null; + if (user?.encryptedPrivateKey) { + PgpPrivateKey = await PushAPI.chat.decryptPGPKey({ + encryptedPGPPrivateKey: user.encryptedPrivateKey, + account: address, + signer, + env, + }); + } - return ( - - {children} - - ); -}; + setPgpPrivateKey(PgpPrivateKey); + })(); + }, [address, env, signer]); -const Spaces: NextPage = () => { - const { SpaceWidgetComponent } = useSpaceComponents(); return ( - +

Spaces UI Test

@@ -52,17 +63,17 @@ const Spaces: NextPage = () => {
-
+ ); }; export default Spaces; const Section = styled.div` - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 20px; - wrap: wrap; -}`; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 20px; + flex-wrap: wrap; +`; diff --git a/packages/restapi/README.md b/packages/restapi/README.md index 41c86ebf8..a9e1cdaea 100644 --- a/packages/restapi/README.md +++ b/packages/restapi/README.md @@ -9,96 +9,96 @@ This package gives access to Push Protocol (Push Nodes) APIs. Visit [Developer D - [How to use in your app?](#how-to-use-in-your-app) - [Installation](#installation) - [Import SDK](#import-sdk) - - [About generating the "signer" object for different platforms](#about-generating-the-signer-object-for-different-platforms) + - [**About generating the "signer" object for different platforms**](#about-generating-the-signer-object-for-different-platforms) - [When using in SERVER-SIDE code:](#when-using-in-server-side-code) - [When using in FRONT-END code:](#when-using-in-front-end-code) - - [About blockchain agnostic address format](#about-blockchain-agnostic-address-format) + - [**About blockchain agnostic address format**](#about-blockchain-agnostic-address-format) - [Chat blockchain agnostic address format](#chat-blockchain-agnostic-address-format) - - [About Push contract addresses](#about-push-contract-addresses) + - [**About Push contract addresses**](#about-push-contract-addresses) - [Push core contract address](#push-core-contract-address) - [Push communicator contract address](#push-communicator-contract-address) - [SDK Features](#sdk-features) - [For Notification](#for-notification) - - [Fetching user notifications](#fetching-user-notifications) - - [Fetching user spam notifications](#fetching-user-spam-notifications) - - [Fetching user subscriptions](#fetching-user-subscriptions) - - [Fetching channel details](#fetching-channel-details) - - [Searching for channel(s)](#searching-for-channels) - - [Opt in to a channel](#opt-in-to-a-channel) - - [Opt out to a channel](#opt-out-to-a-channel) - - [Sending notification](#sending-notification) - - [Direct payload for single recipient(target)](#direct-payload-for-single-recipienttarget) - - [Direct payload for group of recipients(subset)](#direct-payload-for-group-of-recipientssubset) - - [Direct payload for all recipients(broadcast)](#direct-payload-for-all-recipientsbroadcast) - - [IPFS payload for single recipient(target)](#ipfs-payload-for-single-recipienttarget) - - [IPFS payload for group of recipients(subset)](#ipfs-payload-for-group-of-recipientssubset) - - [IPFS payload for all recipients(broadcast)](#ipfs-payload-for-all-recipientsbroadcast) - - [Minimal payload for single recipient(target)](#minimal-payload-for-single-recipienttarget) - - [Minimal payload for a group of recipient(subset)](#minimal-payload-for-a-group-of-recipientsubset) - - [Minimal payload for all recipients(broadcast)](#minimal-payload-for-all-recipientsbroadcast) - - [Graph payload for single recipient(target)](#graph-payload-for-single-recipienttarget) - - [Graph payload for group of recipients(subset)](#graph-payload-for-group-of-recipientssubset) - - [Graph payload for all recipients(broadcast)](#graph-payload-for-all-recipientsbroadcast) + - [**Fetching user notifications**](#fetching-user-notifications) + - [**Fetching user spam notifications**](#fetching-user-spam-notifications) + - [**Fetching user subscriptions**](#fetching-user-subscriptions) + - [**Fetching channel details**](#fetching-channel-details) + - [**Searching for channel(s)**](#searching-for-channels) + - [**Opt in to a channel**](#opt-in-to-a-channel) + - [**Opt out to a channel**](#opt-out-to-a-channel) + - [**Sending notification**](#sending-notification) + - [**Direct payload for single recipient(target)**](#direct-payload-for-single-recipienttarget) + - [**Direct payload for group of recipients(subset)**](#direct-payload-for-group-of-recipientssubset) + - [**Direct payload for all recipients(broadcast)**](#direct-payload-for-all-recipientsbroadcast) + - [**IPFS payload for single recipient(target)**](#ipfs-payload-for-single-recipienttarget) + - [**IPFS payload for group of recipients(subset)**](#ipfs-payload-for-group-of-recipientssubset) + - [**IPFS payload for all recipients(broadcast)**](#ipfs-payload-for-all-recipientsbroadcast) + - [**Minimal payload for single recipient(target)**](#minimal-payload-for-single-recipienttarget) + - [**Minimal payload for a group of recipient(subset)**](#minimal-payload-for-a-group-of-recipientsubset) + - [**Minimal payload for all recipients(broadcast)**](#minimal-payload-for-all-recipientsbroadcast) + - [**Graph payload for single recipient(target)**](#graph-payload-for-single-recipienttarget) + - [**Graph payload for group of recipients(subset)**](#graph-payload-for-group-of-recipientssubset) + - [**Graph payload for all recipients(broadcast)**](#graph-payload-for-all-recipientsbroadcast) - [Notification Helper Utils](#notification-helper-utils) - - [Parsing notifications](#parsing-notifications) + - [**Parsing notifications**](#parsing-notifications) - [Advanced Notifications (WIP)](#advanced-notifications-wip) - [DEPRECATED](#deprecated) - - [Get a channel's subscriber list of addresses](#get-a-channels-subscriber-list-of-addresses) + - [**Get a channel's subscriber list of addresses**](#get-a-channels-subscriber-list-of-addresses) - [For Chat](#for-chat) - - [Create user for chat](#create-user-for-chat) - - [Get user data for chat](#get-user-data-for-chat) - - [Decrypting encrypted pgp private key from user data](#decrypting-encrypted-pgp-private-key-from-user-data) - - [Updating chat user profile](#updating-user-profile) - - [Fetching list of user chats](#fetching-list-of-user-chats) - - [Fetching list of user chat requests](#fetching-list-of-user-chat-requests) - - [Fetching conversation hash between two users](#fetching-conversation-hash-between-two-users) - - [Fetching latest chat between two users](#fetching-latest-chat-between-two-users) - - [Fetching chat history between two users](#fetching-chat-history-between-two-users) - - [To send a message](#to-send-a-message) - - [To approve a chat request](#to-approve-a-chat-request) - - [To create a group](#to-create-a-group) - - [To create a token gated group](#to-create-a-token-gated-group) - - [To update group details](#to-update-group-details) - - [To update token gated group details](#to-update-token-gated-group-details) - - [To get group details by group name](#to-get-group-details-by-group-name) - - [To get group details by chatId](#to-get-group-details-by-chatid) - - [Chat Helper Utils](#chat-helper-utils) - - [Decrypting messages](#decrypting-messages) + - [**Create user for chat**](#create-user-for-chat) + - [**Get user data for chat**](#get-user-data-for-chat) + - [**Decrypting encrypted pgp private key from user data**](#decrypting-encrypted-pgp-private-key-from-user-data) + - [**Updating User Profile**](#updating-user-profile) + - [**Fetching list of user chats**](#fetching-list-of-user-chats) + - [**Fetching list of user chat requests**](#fetching-list-of-user-chat-requests) + - [**Fetching conversation hash between two users**](#fetching-conversation-hash-between-two-users) + - [**Fetching latest chat between two users**](#fetching-latest-chat-between-two-users) + - [**Fetching chat history between two users**](#fetching-chat-history-between-two-users) + - [**To send a message**](#to-send-a-message) + - [**To approve a chat request**](#to-approve-a-chat-request) + - [**To create a group**](#to-create-a-group) + - [**To create a token gated group**](#to-create-a-token-gated-group) + - [**To update group details**](#to-update-group-details) + - [**To update token gated group details**](#to-update-token-gated-group-details) + - [**To get group details by group name**](#to-get-group-details-by-group-name) + - [**To get group details by chatId**](#to-get-group-details-by-chatid) + - [**Chat Helper Utils**](#chat-helper-utils) + - [**Decrypting messages**](#decrypting-messages) - [For Video](#for-video) - - [Instance Variables](#instance-variables) - - [peerInstance](#peerinstance) - - [signer](#signer) - - [chainId](#chainid) - - [pgpPrivateKey](#pgpprivatekey) - - [env](#env) - - [data](#data) - - [setData](#setdata) - - [Methods](#methods) - - [constructor](#constructor) - - [create](#create) - - [request](#request) - - [acceptRequest](#acceptrequest) - - [connect](#connect) - - [disconnect](#disconnect) - - [enableVideo](#enablevideo) - - [enableAudio](#enableaudio) - - [isInitiator](#isinitiator) + - [**Instance Variables**](#instance-variables) + - [**peerInstance**](#peerinstance) + - [**signer**](#signer) + - [**chainId**](#chainid) + - [**pgpPrivateKey**](#pgpprivatekey) + - [**env**](#env) + - [**data**](#data) + - [**setData**](#setdata) + - [**Methods**](#methods) + - [**constructor**](#constructor) + - [**create**](#create) + - [**request**](#request) + - [**acceptRequest**](#acceptrequest) + - [**connect**](#connect) + - [**disconnect**](#disconnect) + - [**enableVideo**](#enablevideo) + - [**enableAudio**](#enableaudio) + - [**isInitiator**](#isinitiator) - [For Spaces](#for-spaces) - - [To create a space](#to-create-a-space) - - [To create a token gated space](#to-create-a-token-gated-space) - - [To update space details](#to-update-space-details) - - [To update token gated space details](#to-update-token-gated-space-details) - - [To get space details by spaceId](#to-get-space-details-by-spaceId) - - [To start a space](#to-start-a-space) - - [To stop a space](#to-stop-a-space) - - [To approve a space request](#to-approve-a-space-request) - - [To add listeners to space](#to-add-listeners-to-space) - - [To remove listeners from space](#to-remove-listeners-from-space) - - [To add speakers to space](#to-add-speakers-to-space) - - [To remove speakers from space](#to-remove-speakers-from-space) - - [Fetching list of user spaces](#fetching-list-of-user-spaces) - - [Fetching list of user space requests](#fetching-list-of-user-space-requests) - - [Fetching list of trending spaces](#fetching-list-of-trending-spaces) + - [**To create a space**](#to-create-a-space) + - [**To create a token gated space**](#to-create-a-token-gated-space) + - [**To update space details**](#to-update-space-details) + - [**To update token gated space details**](#to-update-token-gated-space-details) + - [**To get space details by spaceId**](#to-get-space-details-by-spaceid) + - [**To start a space**](#to-start-a-space) + - [**To stop a space**](#to-stop-a-space) + - [**To approve a space request**](#to-approve-a-space-request) + - [**To add listeners to space**](#to-add-listeners-to-space) + - [**To remove listeners from space**](#to-remove-listeners-from-space) + - [**To add speakers to space**](#to-add-speakers-to-space) + - [**To remove speakers from space**](#to-remove-speakers-from-space) + - [**Fetching list of user spaces**](#fetching-list-of-user-spaces) + - [**Fetching list of user space requests**](#fetching-list-of-user-space-requests) + - [**Fetching list of trending spaces**](#fetching-list-of-trending-spaces) # How to use in your app? diff --git a/packages/restapi/src/lib/payloads/constants.ts b/packages/restapi/src/lib/payloads/constants.ts index 8930689f9..a3c5d9a41 100644 --- a/packages/restapi/src/lib/payloads/constants.ts +++ b/packages/restapi/src/lib/payloads/constants.ts @@ -78,4 +78,11 @@ export enum SPACE_INVITE_ROLES { SPEAKER, } +export enum SPACE_ROLES { + HOST, + CO_HOST, + SPEAKER, + LISTENER +} + export const DEFAULT_DOMAIN = 'push.org'; diff --git a/packages/restapi/src/lib/space/Space.ts b/packages/restapi/src/lib/space/Space.ts index 942271874..a7f16931f 100644 --- a/packages/restapi/src/lib/space/Space.ts +++ b/packages/restapi/src/lib/space/Space.ts @@ -12,8 +12,6 @@ import { requestToBePromoted } from './requestToBePromoted'; import { acceptPromotionRequest } from './acceptPromotionRequest'; import { rejectPromotionRequest } from './rejectPromotionRequest'; import { connectPromotor } from './connectPromotor'; -import { addSpeaker } from './addSpeaker'; -import { removeSpeaker } from './removeSpeaker'; import { join } from './join'; import { leave } from './leave'; import { stop } from './stop'; @@ -24,13 +22,28 @@ import { VideoStreamMerger } from 'video-stream-merger'; import { ChatStatus, EnvOptionsType, + LiveSpaceData, SignerType, - SpaceDTO, SpaceData, + SpaceSpecificData, } from '../types'; import { VIDEO_CALL_TYPE } from '../payloads/constants'; +import getLiveSpaceData from './helpers/getLiveSpaceData'; +import sendLiveSpaceData from './helpers/sendLiveSpaceData'; +import { META_ACTION } from '../types/metaTypes'; +import { broadcastRaisedHand } from './broadcastRaisedHand'; +import { onReceiveMetaMessage } from './onReceiveMetaMessage'; +import { onJoinListener } from './onJoinListener'; +import { pCAIP10ToWallet } from '../helpers'; + +export const initLiveSpaceData: LiveSpaceData = { + host: null, + coHosts: [], + speakers: [], + listeners: [], +}; -const initSpaceSpecificData = { +export const initSpaceSpecificData: SpaceSpecificData = { members: [], pendingMembers: [], contractAddressERC20: null, @@ -48,9 +61,10 @@ const initSpaceSpecificData = { scheduleEnd: null, status: null, inviteeDetails: {}, + liveSpaceData: initLiveSpaceData, }; -export const initSpaceData = { +export const initSpaceData: SpaceData = { ...initSpaceSpecificData, connectionData: initVideoCallData, }; @@ -64,17 +78,13 @@ export interface SpaceConstructorType extends EnvOptionsType { } // declaring the Space class - export class Space extends Video { - /* - - temporarily store the streamKey on the class - - will be used by the host to cast to the stream - */ - // protected streamKey: string | null = null; - +export class Space extends Video { protected mergeStreamObject: VideoStreamMerger | null = null; - protected spaceSpecificData: SpaceDTO; - protected setSpaceSpecificData: (fn: (data: SpaceDTO) => SpaceDTO) => void; + protected spaceSpecificData: SpaceSpecificData; + protected setSpaceSpecificData: ( + fn: (data: SpaceSpecificData) => SpaceSpecificData + ) => void; // will be exposed and should be used from outside the class to change state setSpaceData: (fn: (data: SpaceData) => SpaceData) => void; @@ -96,7 +106,11 @@ export interface SpaceConstructorType extends EnvOptionsType { pgpPrivateKey, env, callType: VIDEO_CALL_TYPE.PUSH_SPACE, - onReceiveStream: (receivedStream: MediaStream) => { + onReceiveStream: async ( + receivedStream: MediaStream, + senderAddress: string, + audio: boolean | null + ) => { // for a space, that has started broadcast & the local peer is the host if ( this.spaceSpecificData.status === ChatStatus.ACTIVE && @@ -104,6 +118,34 @@ export interface SpaceConstructorType extends EnvOptionsType { this.data.meta.broadcast.hostAddress === this.data.local.address ) { addToMergedStream(this.mergeStreamObject!, receivedStream); + + // update live space info + const oldLiveSpaceData = await getLiveSpaceData({ + localAddress: this.data.local.address, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + }); + const updatedLiveSpaceData = produce(oldLiveSpaceData, (draft) => { + // TODO: Create distinction between speakers and co hosts + draft.speakers.push({ + address: senderAddress, + audio, + emojiReactions: null, + }); + }); + await sendLiveSpaceData({ + liveSpaceData: updatedLiveSpaceData, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + signer: this.signer, + action: META_ACTION.PROMOTE_TO_ADMIN, // TODO: Add a meta action for SPEAKER_JOINED + }); + this.setSpaceSpecificData(() => ({ + ...this.spaceSpecificData, + liveSpaceData: updatedLiveSpaceData, + })); } }, setData: function () { @@ -169,7 +211,7 @@ export interface SpaceConstructorType extends EnvOptionsType { // set the local address inside video call 'data' this.setData((oldVideoCallData) => { return produce(oldVideoCallData, (draft) => { - draft.local.address = address; + draft.local.address = pCAIP10ToWallet(address); }); }); @@ -178,8 +220,7 @@ export interface SpaceConstructorType extends EnvOptionsType { // init the spaceSpecificData class variable this.spaceSpecificData = initSpaceSpecificData; - }; - + } // adding instance methods @@ -193,6 +234,12 @@ export interface SpaceConstructorType extends EnvOptionsType { public start = start; + public onReceiveMetaMessage = onReceiveMetaMessage; + + // host will call this function from socket + // will fire a meta message if a new listener has joined the space + public onJoinListener = onJoinListener; + // to promote a listener to a speaker/co-host public inviteToPromote = inviteToPromote; public acceptPromotionInvite = acceptPromotionInvite; @@ -201,17 +248,11 @@ export interface SpaceConstructorType extends EnvOptionsType { // listener requests to be promoted to a speaker public requestToBePromoted = requestToBePromoted; + public broadcastRaisedHand = broadcastRaisedHand; // will be called by the host after receiving the request to be promoted public acceptPromotionRequest = acceptPromotionRequest; public connectPromotor = connectPromotor; public rejectPromotionRequest = rejectPromotionRequest; - /* - - add/remove speaker to the space group as admins - - these methods are only to be used when the space hasnt started yet - */ - public addSpeaker = addSpeaker; - public removeSpeaker = removeSpeaker; - /* - add/remove co-host to the space group as admins - add/remove them from the meta message diff --git a/packages/restapi/src/lib/space/addSpeaker.ts b/packages/restapi/src/lib/space/addSpeaker.ts deleted file mode 100644 index 37924f724..000000000 --- a/packages/restapi/src/lib/space/addSpeaker.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { EnvOptionsType } from '../types'; -import { groupDtoToSpaceDto } from '../chat/helpers'; -import { addAdmins } from '../chat/addAdmins'; - -import type Space from './Space'; - -export interface AddSpeakerType extends EnvOptionsType { - address: string; -} - -export async function addSpeaker( - this: Space, - options: AddSpeakerType -): Promise { - const { address } = options; - try { - const group = await addAdmins({ - chatId: this.spaceSpecificData.spaceId, - admins: [address], - signer: this.signer, - env: this.env, - pgpPrivateKey: this.pgpPrivateKey, - }); - - // update space specific data - this.setSpaceSpecificData(() => groupDtoToSpaceDto(group)); - } catch (err) { - console.error( - `[Push SDK] - API - Error - API ${addSpeaker.name} -: `, - err - ); - throw Error(`[Push SDK] - API - Error - API ${addSpeaker.name} -: ${err}`); - } -} diff --git a/packages/restapi/src/lib/space/broadcastRaisedHand.ts b/packages/restapi/src/lib/space/broadcastRaisedHand.ts new file mode 100644 index 000000000..38c5251ea --- /dev/null +++ b/packages/restapi/src/lib/space/broadcastRaisedHand.ts @@ -0,0 +1,40 @@ +import { produce } from 'immer'; +import type Space from './Space'; +import getLiveSpaceData from './helpers/getLiveSpaceData'; +import sendLiveSpaceData from './helpers/sendLiveSpaceData'; +import { META_ACTION } from '../types/metaTypes'; + +export interface BroadcastRaisedHandType { + promoteeAddress: string; +} + +export async function broadcastRaisedHand( + this: Space, + options: BroadcastRaisedHandType +) { + const { promoteeAddress } = options || {}; + + // update live space info + const oldLiveSpaceData = await getLiveSpaceData({ + localAddress: this.data.local.address, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + }); + const updatedLiveSpaceData = produce(oldLiveSpaceData, (draft) => { + const listnerIndex = draft.listeners.findIndex(listner => listner.address === promoteeAddress); + draft.listeners[listnerIndex].handRaised = true; + }); + await sendLiveSpaceData({ + liveSpaceData: updatedLiveSpaceData, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + signer: this.signer, + action: META_ACTION.USER_INTERACTION, + }); + this.setSpaceSpecificData(() => ({ + ...this.spaceSpecificData, + liveSpaceData: updatedLiveSpaceData, + })); +} diff --git a/packages/restapi/src/lib/space/helpers/getLiveSpaceData.ts b/packages/restapi/src/lib/space/helpers/getLiveSpaceData.ts new file mode 100644 index 000000000..4417ba0c1 --- /dev/null +++ b/packages/restapi/src/lib/space/helpers/getLiveSpaceData.ts @@ -0,0 +1,64 @@ +import { conversationHash, history } from '../../chat'; +import { MessageType } from '../../constants'; +import { EnvOptionsType, LiveSpaceData } from '../../types'; +import { initLiveSpaceData } from '../Space'; + +interface GetLatestMessageType extends EnvOptionsType { + localAddress: string; + spaceId: string; + pgpPrivateKey: string; +} + +const getLiveSpaceData = async ({ + localAddress, + spaceId, + pgpPrivateKey, + env, +}: GetLatestMessageType) => { + const threadhash = ( + await conversationHash({ + account: localAddress, + conversationId: spaceId, + env, + }) + ).threadHash; + + let liveSpaceData = initLiveSpaceData; + + // fetch the message history to retrieve the latest meta message + for (let i = 0; i < 10; i++) { + const messages = await history({ + threadhash, + account: localAddress, + pgpPrivateKey, + toDecrypt: true, + env, + }); + + let latestMetaMessage = null; + for (const message of messages) { + if ( + message.messageType === MessageType.META && + typeof message.messageObj === 'object' && + message.messageObj !== null + ) { + latestMetaMessage = message; + break; + } + } + + if ( + latestMetaMessage !== null && + typeof latestMetaMessage.messageObj === 'object' && + latestMetaMessage.messageObj !== null + ) { + // found the latest meta message + liveSpaceData = latestMetaMessage.messageObj?.meta?.info + ?.arbitrary as LiveSpaceData; + } + } + + return liveSpaceData; +}; + +export default getLiveSpaceData; diff --git a/packages/restapi/src/lib/space/helpers/getPlainAddress.ts b/packages/restapi/src/lib/space/helpers/getPlainAddress.ts new file mode 100644 index 000000000..d24da128a --- /dev/null +++ b/packages/restapi/src/lib/space/helpers/getPlainAddress.ts @@ -0,0 +1,5 @@ +const getPlainAddress = (prefixedAddress: string) => { + return prefixedAddress.replace('eip155:', ''); +}; + +export default getPlainAddress; diff --git a/packages/restapi/src/lib/space/helpers/getSpaceListeners.ts b/packages/restapi/src/lib/space/helpers/getSpaceListeners.ts new file mode 100644 index 000000000..e989dd2a7 --- /dev/null +++ b/packages/restapi/src/lib/space/helpers/getSpaceListeners.ts @@ -0,0 +1,25 @@ +const getSpaceListeners = ( + members: { + wallet: string; + publicKey: string; + isSpeaker: boolean; + image: string; + }[] +) => { + const listeners: { + wallet: string; + publicKey: string; + isSpeaker: boolean; + image: string; + }[] = []; + + members.forEach((member) => { + if (!member.isSpeaker) { + listeners.push(member); + } + }); + + return listeners; +}; + +export default getSpaceListeners; diff --git a/packages/restapi/src/lib/space/helpers/sendLiveSpaceData.ts b/packages/restapi/src/lib/space/helpers/sendLiveSpaceData.ts new file mode 100644 index 000000000..7ca8b99dd --- /dev/null +++ b/packages/restapi/src/lib/space/helpers/sendLiveSpaceData.ts @@ -0,0 +1,41 @@ +import { send } from '../../chat'; +import { MessageType } from '../../constants'; +import { EnvOptionsType, LiveSpaceData, SignerType } from '../../types'; +import { META_ACTION } from '../../types/metaTypes'; + +interface SendLiveSpaceData extends EnvOptionsType { + liveSpaceData?: LiveSpaceData; + action: META_ACTION; + spaceId: string; + pgpPrivateKey: string; + signer: SignerType; +} + +const sendLiveSpaceData = async ({ + liveSpaceData, + action, + spaceId, + pgpPrivateKey, + signer, + env, +}: SendLiveSpaceData) => { + await send({ + receiverAddress: spaceId, + pgpPrivateKey, + env, + signer, + messageType: MessageType.META, + messageObj: { + content: 'PUSH SPACE META MESSAGE', + meta: { + action, + info: { + affected: [], + arbitrary: liveSpaceData, + }, + }, + }, + }); +}; + +export default sendLiveSpaceData; diff --git a/packages/restapi/src/lib/space/index.ts b/packages/restapi/src/lib/space/index.ts index d2824b32e..e2ea49306 100644 --- a/packages/restapi/src/lib/space/index.ts +++ b/packages/restapi/src/lib/space/index.ts @@ -1,3 +1,5 @@ +export * from './helpers/getPlainAddress'; + export * from './spaces'; export * from './trending'; export * from './get'; diff --git a/packages/restapi/src/lib/space/initialize.ts b/packages/restapi/src/lib/space/initialize.ts index 85f3dc682..27dc51e5d 100644 --- a/packages/restapi/src/lib/space/initialize.ts +++ b/packages/restapi/src/lib/space/initialize.ts @@ -1,5 +1,7 @@ +import { ChatStatus } from '../types'; import type Space from './Space'; import { get } from './get'; +import getLiveSpaceData from './helpers/getLiveSpaceData'; export interface InitializeType { spaceId: string; @@ -13,5 +15,17 @@ export async function initialize(this: Space, options: InitializeType) { env: this.env, }); - this.setSpaceSpecificData(() => space); + let liveSpaceData = this.spaceSpecificData.liveSpaceData; + + // if the space is active then fetch the latest meta message and update the live space data state + if (space.status === ChatStatus.ACTIVE) { + liveSpaceData = await getLiveSpaceData({ + localAddress: this.data.local.address, + spaceId, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + }); + } + + this.setSpaceSpecificData(() => ({ ...space, liveSpaceData })); } diff --git a/packages/restapi/src/lib/space/join.ts b/packages/restapi/src/lib/space/join.ts index 7e3342c59..8bb82c441 100644 --- a/packages/restapi/src/lib/space/join.ts +++ b/packages/restapi/src/lib/space/join.ts @@ -1,9 +1,9 @@ -import { - SPACE_REQUEST_TYPE, -} from '../payloads/constants'; +import { SPACE_REQUEST_TYPE } from '../payloads/constants'; import { ChatStatus } from '../types'; import { approve } from './approve'; import { get } from './get'; +import getIncomingIndexFromAddress from '../video/helpers/getIncomingIndexFromAddress'; +import getPlainAddress from './helpers/getPlainAddress'; import type Space from './Space'; /** @@ -25,8 +25,9 @@ export async function join(this: Space) { let isSpeaker = false; let isListner = false; + const localAddress = getPlainAddress(this.data.local.address); space.members.forEach((member) => { - if (member.wallet === this.data.local.address) { + if (getPlainAddress(member.wallet) === localAddress) { if (member.isSpeaker) { isSpeaker = true; } else { @@ -37,17 +38,38 @@ export async function join(this: Space) { let isSpeakerPending = false; space.pendingMembers.forEach((pendingMember) => { if ( - pendingMember.wallet === this.data.local.address && + getPlainAddress(pendingMember.wallet) === localAddress && pendingMember.isSpeaker ) { isSpeakerPending = true; } }); + console.log( + 'ISSPEAKER', + isSpeaker, + 'isListner', + isListner, + 'isSpeakerPending', + isSpeakerPending + ); + + const hostAddress = getPlainAddress(space.spaceCreator); + const incomingIndex = getIncomingIndexFromAddress( + this.data.incoming, + hostAddress + ); + + // check if we arent already connected to the host + if ((isSpeaker || isSpeakerPending) && incomingIndex > -1) { + return Promise.resolve(); + } + // acc to the found role (speaker or listner), executing req logic // if speaker is pending then approve first or if listner is pending/not found then approve first - if (isSpeakerPending || !isListner) { + if (!isSpeaker && !isListner) { + console.log('CALLING APPROVE'); await approve({ signer: this.signer, pgpPrivateKey: this.pgpPrivateKey, @@ -58,7 +80,7 @@ export async function join(this: Space) { if (isSpeaker || isSpeakerPending) { // Call the host and join the mesh connection - const hostAddress = space.spaceCreator.replace('eip155:', ''); + console.log('CALLING REQUEST'); await this.request({ senderAddress: this.data.local.address, recipientAddress: hostAddress, @@ -74,8 +96,12 @@ export async function join(this: Space) { spaceId: this.spaceSpecificData.spaceId, env: this.env, }); + console.log('UPDATED SPACE', updatedSpace); // update space specific data - this.setSpaceSpecificData(() => updatedSpace); + this.setSpaceSpecificData(() => ({ + ...updatedSpace, + liveSpaceData: this.spaceSpecificData.liveSpaceData, + })); } catch (err) { console.error(`[Push SDK] - API - Error - API ${join.name} -: `, err); throw Error(`[Push SDK] - API - Error - API ${join.name} -: ${err}`); diff --git a/packages/restapi/src/lib/space/leave.ts b/packages/restapi/src/lib/space/leave.ts index 12121a86c..abbfc4b0b 100644 --- a/packages/restapi/src/lib/space/leave.ts +++ b/packages/restapi/src/lib/space/leave.ts @@ -10,7 +10,7 @@ export async function leave(this: Space): Promise { // handle the case where a listner is leaving // disconnect with every incoming peer in the mesh connection - this.data.incoming.forEach(({ address }) => { + this.data.incoming.slice(1).forEach(({ address }) => { this.disconnect({ peerAddress: address, details: { diff --git a/packages/restapi/src/lib/space/onJoinListener.ts b/packages/restapi/src/lib/space/onJoinListener.ts new file mode 100644 index 000000000..176dfdcbc --- /dev/null +++ b/packages/restapi/src/lib/space/onJoinListener.ts @@ -0,0 +1,71 @@ +import { pCAIP10ToWallet } from '../helpers'; +import getLiveSpaceData from './helpers/getLiveSpaceData'; +import getSpaceListeners from './helpers/getSpaceListeners'; +import sendLiveSpaceData from './helpers/sendLiveSpaceData'; + +import { ListenerPeer, SpaceDTO } from '../types'; +import { META_ACTION } from '../types/metaTypes'; +import type Space from './Space'; + +export interface OnJoinListenerType { + receivedSpaceData: SpaceDTO; +} + +export async function onJoinListener(this: Space, options: OnJoinListenerType) { + const { receivedSpaceData } = options || {}; + + if ( + pCAIP10ToWallet(this.spaceSpecificData.spaceCreator) !== + this.data.local.address + ) { + return; + } + + // check whether any new listener has joined by comparing this.spaceSpecificData and receivedLiveSpaceData + const fetchedLiveSpaceData = await getLiveSpaceData({ + localAddress: this.data.local.address, + spaceId: this.spaceSpecificData.spaceId, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + }); + const localListeners = fetchedLiveSpaceData.listeners; + const receivedListeners = getSpaceListeners(receivedSpaceData.members); + + const localListenerAddresses: { + [key: string]: number; + } = {}; + localListeners.map((listener, index) => { + localListenerAddresses[pCAIP10ToWallet(listener.address)] = index; + }); + + const updatedListeners: ListenerPeer[] = []; + + let areListenersChanged = false; + + for (const listener of receivedListeners) { + const index = localListenerAddresses[pCAIP10ToWallet(listener.wallet)]; + + if (!areListenersChanged) areListenersChanged = Boolean(!index); + + updatedListeners.push( + index + ? localListeners[index] + : { + address: listener.wallet, + handRaised: false, + emojiReactions: null, + } + ); + } + + if (areListenersChanged) { + sendLiveSpaceData({ + spaceId: this.spaceSpecificData.spaceId, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + signer: this.signer, + liveSpaceData: { ...fetchedLiveSpaceData, listeners: updatedListeners }, + action: META_ACTION.ADD_LISTENER, + }); + } +} diff --git a/packages/restapi/src/lib/space/onReceiveMetaMessage.ts b/packages/restapi/src/lib/space/onReceiveMetaMessage.ts new file mode 100644 index 000000000..26b626592 --- /dev/null +++ b/packages/restapi/src/lib/space/onReceiveMetaMessage.ts @@ -0,0 +1,18 @@ +import { LiveSpaceData } from '../types'; +import type Space from './Space'; + +export interface OnReceiveMetaMessageType { + receivedLiveSpaceData: LiveSpaceData; +} + +export async function onReceiveMetaMessage( + this: Space, + options: OnReceiveMetaMessageType +) { + const { receivedLiveSpaceData } = options || {}; + + this.setSpaceSpecificData(() => ({ + ...this.spaceSpecificData, + liveSpaceData: receivedLiveSpaceData, + })); +} diff --git a/packages/restapi/src/lib/space/rejectPromotionRequest.ts b/packages/restapi/src/lib/space/rejectPromotionRequest.ts index b4eddee56..abc5cc7bf 100644 --- a/packages/restapi/src/lib/space/rejectPromotionRequest.ts +++ b/packages/restapi/src/lib/space/rejectPromotionRequest.ts @@ -1,4 +1,8 @@ +import { produce } from 'immer'; import type Space from './Space'; +import getLiveSpaceData from './helpers/getLiveSpaceData'; +import sendLiveSpaceData from './helpers/sendLiveSpaceData'; +import { META_ACTION } from '../types/metaTypes'; export interface RejectPromotionRequestType { promoteeAddress: string; @@ -14,4 +18,28 @@ export async function rejectPromotionRequest( this.disconnect({ peerAddress: promoteeAddress, }); + + // update live space info + const oldLiveSpaceData = await getLiveSpaceData({ + localAddress: this.data.local.address, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + }); + const updatedLiveSpaceData = produce(oldLiveSpaceData, (draft) => { + const listnerIndex = draft.listeners.findIndex(listner => listner.address === promoteeAddress); + draft.listeners[listnerIndex].handRaised = false; + }); + await sendLiveSpaceData({ + liveSpaceData: updatedLiveSpaceData, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + spaceId: this.spaceSpecificData.spaceId, + signer: this.signer, + action: META_ACTION.USER_INTERACTION, // TODO: Add a reject request type + }); + this.setSpaceSpecificData(() => ({ + ...this.spaceSpecificData, + liveSpaceData: updatedLiveSpaceData, + })); } diff --git a/packages/restapi/src/lib/space/removeSpeaker.ts b/packages/restapi/src/lib/space/removeSpeaker.ts deleted file mode 100644 index 84c214d02..000000000 --- a/packages/restapi/src/lib/space/removeSpeaker.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { EnvOptionsType } from '../types'; -import { groupDtoToSpaceDto } from '../chat/helpers'; -import { - removeAdmins -} from '../chat/removeAdmins'; -import type Space from './Space'; - -export interface RemoveSpeakerType extends EnvOptionsType { - address: string; -} - -export async function removeSpeaker( - this: Space, - options: RemoveSpeakerType -): Promise { - const { address } = options; - try { - const group = await removeAdmins({ - chatId: this.spaceSpecificData.spaceId, - admins: [address], - signer: this.signer, - env: this.env, - pgpPrivateKey: this.pgpPrivateKey, - }); - - // update space specific data - this.setSpaceSpecificData(() => groupDtoToSpaceDto(group)); - } catch (err) { - console.error( - `[Push SDK] - API - Error - API ${removeSpeaker.name} -: `, - err - ); - throw Error( - `[Push SDK] - API - Error - API ${removeSpeaker.name} -: ${err}` - ); - } -} diff --git a/packages/restapi/src/lib/space/requestToBePromoted.ts b/packages/restapi/src/lib/space/requestToBePromoted.ts index b52992841..80b9c7d13 100644 --- a/packages/restapi/src/lib/space/requestToBePromoted.ts +++ b/packages/restapi/src/lib/space/requestToBePromoted.ts @@ -3,7 +3,6 @@ import type Space from './Space'; export interface RequestToBePromotedType { role: SPACE_INVITE_ROLES; - spaceId: string; promotorAddress: string; } @@ -11,13 +10,13 @@ export async function requestToBePromoted( this: Space, options: RequestToBePromotedType ) { - const { role, spaceId, promotorAddress } = options || {}; + const { role, promotorAddress } = options || {}; // requesting host to include local computer into the mesh connection this.request({ senderAddress: this.data.local.address, recipientAddress: promotorAddress, - chatId: spaceId, + chatId: this.spaceSpecificData.spaceId, details: { type: SPACE_REQUEST_TYPE.REQUEST_TO_PROMOTE, data: { diff --git a/packages/restapi/src/lib/space/start.ts b/packages/restapi/src/lib/space/start.ts index f15f28449..79a641030 100644 --- a/packages/restapi/src/lib/space/start.ts +++ b/packages/restapi/src/lib/space/start.ts @@ -1,4 +1,9 @@ -import { EnvOptionsType, SignerType, ChatStatus } from '../types'; +import { + EnvOptionsType, + SignerType, + ChatStatus, + LiveSpaceData, +} from '../types'; import { groupDtoToSpaceDto, getSpacesMembersList, @@ -19,6 +24,9 @@ export interface StartSpaceType extends EnvOptionsType { import type Space from './Space'; import { produce } from 'immer'; +import { pCAIP10ToWallet } from '../helpers'; +import { META_ACTION } from '../types/metaTypes'; +import sendLiveSpaceData from './helpers/sendLiveSpaceData'; type StartType = { livepeerApiKey: string; @@ -28,8 +36,6 @@ export async function start(this: Space, options: StartType): Promise { const { livepeerApiKey } = options || {}; try { - // TODO: Only allow the host to execute this function - // host should have there audio stream if (!this.data.local.stream) { throw new Error('Local audio stream not found'); @@ -46,6 +52,11 @@ export async function start(this: Space, options: StartType): Promise { ); } + // Only host is allowed to start a space + if (this.data.local.address !== pCAIP10ToWallet(space.spaceCreator)) { + throw new Error('Only host is allowed to start a space'); + } + const convertedMembers = getSpacesMembersList( space.members, space.pendingMembers @@ -70,11 +81,32 @@ export async function start(this: Space, options: StartType): Promise { status: ChatStatus.ACTIVE, }); + const liveSpaceData: LiveSpaceData = { + host: { + address: this.data.local.address, + audio: this.data.local.audio, + emojiReactions: null, + }, + coHosts: [], + speakers: [], + listeners: [], + }; + + await sendLiveSpaceData({ + liveSpaceData, + action: META_ACTION.CREATE_SPACE, + spaceId: this.spaceSpecificData.spaceId, + signer: this.signer, + pgpPrivateKey: this.pgpPrivateKey, + env: this.env, + }); + // update space data this.setSpaceData((oldSpaceData) => { return produce(oldSpaceData, (draft) => { draft = { ...groupDtoToSpaceDto(group), + liveSpaceData, connectionData: draft.connectionData, }; draft.connectionData.meta.broadcast = { diff --git a/packages/restapi/src/lib/space/stop.ts b/packages/restapi/src/lib/space/stop.ts index 4f69914ec..9a3e82114 100644 --- a/packages/restapi/src/lib/space/stop.ts +++ b/packages/restapi/src/lib/space/stop.ts @@ -48,7 +48,10 @@ export async function stop(this: Space): Promise { }); // update space specific data - this.setSpaceSpecificData(() => groupDtoToSpaceDto(group)); + this.setSpaceSpecificData(() => ({ + ...groupDtoToSpaceDto(group), + liveSpaceData: this.spaceSpecificData.liveSpaceData, + })); // stop livepeer playback @@ -56,7 +59,7 @@ export async function stop(this: Space): Promise { - disconnect with every incoming peer in the mesh connection - other peers should also end their connections as we want to destroy the mesh connection */ - this.data.incoming.forEach(({ address }) => { + this.data.incoming.slice(1).forEach(({ address }) => { this.disconnect({ peerAddress: address, details: { diff --git a/packages/restapi/src/lib/space/update.ts b/packages/restapi/src/lib/space/update.ts index 266baf456..2ccbea8ef 100644 --- a/packages/restapi/src/lib/space/update.ts +++ b/packages/restapi/src/lib/space/update.ts @@ -63,7 +63,10 @@ export async function update( }); // update space specific data - this.setSpaceSpecificData(() => groupDtoToSpaceDto(group)); + this.setSpaceSpecificData(() => ({ + ...groupDtoToSpaceDto(group), + liveSpaceData: this.spaceSpecificData.liveSpaceData, + })); } catch (err) { console.error(`[Push SDK] - API - Error - API ${update.name} -: `, err); throw Error(`[Push SDK] - API - Error - API ${update.name} -: ${err}`); diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index a3b440803..e99f6d158 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -306,7 +306,7 @@ export interface Member { export enum ChatStatus { ACTIVE = 'ACTIVE', PENDING = 'PENDING', - ENDED = 'ENDED' + ENDED = 'ENDED', } export interface GroupDTO { members: { @@ -368,7 +368,34 @@ export interface SpaceDTO { inviteeDetails?: { [key: string]: SPACE_INVITE_ROLES }; } -export interface SpaceData extends SpaceDTO { +export interface Peer { + address: string; + emojiReactions: { + emoji: string; + expiresIn: string; + } | null; +} + +export interface ListenerPeer extends Peer { + handRaised: boolean; +} + +export interface AdminPeer extends Peer { + audio: boolean | null; +} + +export interface LiveSpaceData { + host: AdminPeer | null; + coHosts: AdminPeer[]; + speakers: AdminPeer[]; + listeners: ListenerPeer[]; +} + +export interface SpaceSpecificData extends SpaceDTO { + liveSpaceData: LiveSpaceData; +} + +export interface SpaceData extends SpaceSpecificData { connectionData: VideoCallData; } diff --git a/packages/restapi/src/lib/types/metaTypes.ts b/packages/restapi/src/lib/types/metaTypes.ts index e87c1ee7b..a5fc4ddf9 100644 --- a/packages/restapi/src/lib/types/metaTypes.ts +++ b/packages/restapi/src/lib/types/metaTypes.ts @@ -2,7 +2,7 @@ * This file defines the type for meta property for a Push Chat message */ -const enum META_ACTION { +export const enum META_ACTION { /** * DEFAULT GROUP ACTIONS */ diff --git a/packages/restapi/src/lib/video/Video.ts b/packages/restapi/src/lib/video/Video.ts index f1d340979..df7618e72 100644 --- a/packages/restapi/src/lib/video/Video.ts +++ b/packages/restapi/src/lib/video/Video.ts @@ -55,6 +55,7 @@ export const initVideoCallData: VideoCallData = { video: null, address: '', }, + // TODO: Remove the default element in incoming array incoming: [ { stream: null, @@ -74,7 +75,7 @@ export class Video { protected pgpPrivateKey: string; protected env: ENV; protected callType: VIDEO_CALL_TYPE; - protected onReceiveStream: (receivedStream: MediaStream) => void; + protected onReceiveStream: (receivedStream: MediaStream, senderAddress: string, audio:boolean | null) => Promise; // storing the peer instance private peerInstances: { @@ -91,8 +92,8 @@ export class Video { env = Constants.ENV.PROD, setData, callType = VIDEO_CALL_TYPE.PUSH_VIDEO, - onReceiveStream = () => { - return; + onReceiveStream = async () => { + return Promise.resolve(); }, }: { signer: SignerType; @@ -101,7 +102,7 @@ export class Video { setData: (fn: (data: VideoCallData) => VideoCallData) => void; env?: ENV; callType?: VIDEO_CALL_TYPE; - onReceiveStream?: (receivedStream: MediaStream) => void; + onReceiveStream?: (receivedStream: MediaStream, senderAddress: string, audio:boolean | null) => Promise; }) { this.signer = signer; this.chainId = chainId; @@ -257,14 +258,33 @@ export class Video { value: this.data.local.audio, }) ); + // send the addresses the local peer is connected to remote peer + const connectedAddresses = getConnectedAddresses({ + incomingPeers: this.data.incoming, + }); + console.log( + 'REQUEST - SENDING THE CONNECTED ADDRESSES', + 'connectedAddresses', + connectedAddresses + ); + this.peerInstances[recipientAddress].send( + JSON.stringify({ + type: 'connectedAddresses', + value: connectedAddresses, + }) + ); }); this.peerInstances[recipientAddress].on('data', (data: any) => { if (isJSON(data)) { const parsedData = JSON.parse(data); - if (parsedData.type === 'connectedAddress') { - console.log('CONNECTED ADDRESSES', parsedData.value); + if (parsedData.type === 'connectedAddresses') { + console.log( + 'REQUEST - RECEIVING CONNECTED ADDRESSES', + 'CONNECTED ADDRESSES', + parsedData.value + ); const receivedConnectedAddresses = parsedData.value; const localConnectedAddresses = getConnectedAddresses({ @@ -278,17 +298,15 @@ export class Video { localConnectedAddresses, receivedConnectedAddresses, }); - for (const connectToAddress of connectToAddresses) { - this.request({ - senderAddress, - recipientAddress: connectToAddress, - chatId, - details: { - type: SPACE_REQUEST_TYPE.ESTABLISH_MESH, - data: {}, - }, - }); - } + this.request({ + senderAddress, + recipientAddress: connectToAddresses, + chatId, + details: { + type: SPACE_REQUEST_TYPE.ESTABLISH_MESH, + data: {}, + }, + }); } if (parsedData.type === 'isVideoOn') { @@ -371,13 +389,13 @@ export class Video { 'stream', (currentStream: MediaStream) => { console.log('received incoming stream', currentStream); - this.onReceiveStream(currentStream); + const incomingIndex = getIncomingIndexFromAddress( + this.data.incoming, + recipientAddress + ); + this.onReceiveStream(currentStream, recipientAddress, this.data.incoming[incomingIndex].audio) this.setData((oldData) => { return produce(oldData, (draft) => { - const incomingIndex = getIncomingIndexFromAddress( - oldData.incoming, - recipientAddress - ); draft.incoming[incomingIndex].stream = currentStream; }); }); @@ -478,6 +496,7 @@ export class Video { status: VideoCallStatus.RETRY_INITIALIZED, chatId, signalData: null, + callType: this.callType, env: this.env, } ); @@ -527,6 +546,22 @@ export class Video { }) ); + // send the addresses the local peer is connected to remote peer + const connectedAddresses = getConnectedAddresses({ + incomingPeers: this.data.incoming, + }); + console.log( + 'ACCEPT REQUEST - SENDING THE CONNECTED ADDRESSES', + 'connectedAddresses', + connectedAddresses + ); + this.peerInstances[recipientAddress].send( + JSON.stringify({ + type: 'connectedAddresses', + value: connectedAddresses, + }) + ); + // set videoCallInfo state with status connected for the receiver's end this.setData((oldData) => { return produce(oldData, (draft) => { @@ -543,8 +578,12 @@ export class Video { if (isJSON(data)) { const parsedData = JSON.parse(data); - if (parsedData.type === 'connectedAddress') { - console.log('CONNECTED ADDRESSES', parsedData.value); + if (parsedData.type === 'connectedAddresses') { + console.log( + 'ACCEPT REQUEST - RECEIVING CONNECTED ADDRESSES', + 'CONNECTED ADDRESSES', + parsedData.value + ); const receivedConnectedAddresses = parsedData.value; const localConnectedAddresses = getConnectedAddresses({ @@ -558,25 +597,15 @@ export class Video { localConnectedAddresses, receivedConnectedAddresses, }); - for (const connectToAddress of connectToAddresses) { - this.request({ - senderAddress, - recipientAddress: connectToAddress, - chatId, - details: { - type: SPACE_REQUEST_TYPE.ESTABLISH_MESH, - data: {}, - }, - }); - } - - // send the addresses the local peer is connected to remote peer - this.peerInstances[recipientAddress].send( - JSON.stringify({ - type: 'connectedAddresses', - value: localConnectedAddresses, - }) - ); + this.request({ + senderAddress, + recipientAddress: connectToAddresses, + chatId, + details: { + type: SPACE_REQUEST_TYPE.ESTABLISH_MESH, + data: {}, + }, + }); } if (parsedData.type === 'isVideoOn') { @@ -659,13 +688,13 @@ export class Video { 'stream', (currentStream: MediaStream) => { console.log('received incoming stream', currentStream); - this.onReceiveStream(currentStream); + const incomingIndex = getIncomingIndexFromAddress( + this.data.incoming, + recipientAddress + ); + this.onReceiveStream(currentStream, recipientAddress, this.data.incoming[incomingIndex].audio); this.setData((oldData) => { return produce(oldData, (draft) => { - const incomingIndex = getIncomingIndexFromAddress( - oldData.incoming, - recipientAddress - ); draft.incoming[incomingIndex].stream = currentStream; }); }); @@ -707,17 +736,6 @@ export class Video { this.peerInstances[peerAddress]?.signal(signalData); - // send the addresses the local peer is connected to remote peer - const connectedAddresses = getConnectedAddresses({ - incomingPeers: this.data.incoming, - }); - this.peerInstances[peerAddress].send( - JSON.stringify({ - type: 'connectedAddresses', - value: connectedAddresses, - }) - ); - // set videoCallInfo state with status connected for the caller's end this.setData((oldData) => { return produce(oldData, (draft) => { @@ -737,6 +755,8 @@ export class Video { const { peerAddress, details } = options || {}; try { + console.log("DISCONNECT OPTIONS", options); + const incomingIndex = getIncomingIndexFromAddress( this.data.incoming, peerAddress @@ -800,20 +820,16 @@ export class Video { if (this.data.local.video !== state) { // need to change the video state - const incomingIndex = getIncomingIndexFromAddress( - this.data.incoming, - peerAddress - ); - - if ( - this.data.incoming[incomingIndex].status === VideoCallStatus.CONNECTED - ) { - this.peerInstances[peerAddress]?.send( - JSON.stringify({ - type: 'isVideoOn', - value: state, - }) - ); + // signal all the connected peers that the local peer has changed their video state + for (const incomingPeer of this.data.incoming) { + if (incomingPeer.status === VideoCallStatus.CONNECTED) { + this.peerInstances[incomingPeer.address]?.send( + JSON.stringify({ + type: 'isVideoOn', + value: state, + }) + ); + } } if (this.data.local.stream) { if (state) { @@ -836,17 +852,13 @@ export class Video { if (this.data.local.audio !== state) { // need to change the audio state - const incomingIndex = getIncomingIndexFromAddress( - this.data.incoming, - peerAddress - ); - - if ( - this.data.incoming[incomingIndex].status === VideoCallStatus.CONNECTED - ) { - this.peerInstances[peerAddress]?.send( - JSON.stringify({ type: 'isAudioOn', value: state }) - ); + // signal all the connected peers that the local peer has changed their audio state + for (const incomingPeer of this.data.incoming) { + if (incomingPeer.status === VideoCallStatus.CONNECTED) { + this.peerInstances[incomingPeer.address]?.send( + JSON.stringify({ type: 'isAudioOn', value: state }) + ); + } } if (this.data.local.stream) { if (state) { diff --git a/packages/restapi/src/lib/video/helpers/getConnectedAddresses.ts b/packages/restapi/src/lib/video/helpers/getConnectedAddresses.ts index 7e9d9a149..89671145d 100644 --- a/packages/restapi/src/lib/video/helpers/getConnectedAddresses.ts +++ b/packages/restapi/src/lib/video/helpers/getConnectedAddresses.ts @@ -1,3 +1,4 @@ +import getPlainAddress from '../../space/helpers/getPlainAddress'; import { PeerData, VideoCallStatus } from '../../types'; const getConnectedAddresses = ({ @@ -8,7 +9,7 @@ const getConnectedAddresses = ({ const connectedAddresses: string[] = []; incomingPeers.forEach((incomingPeer) => { if (incomingPeer.status === VideoCallStatus.CONNECTED) { - connectedAddresses.push(incomingPeer.address); + connectedAddresses.push(getPlainAddress(incomingPeer.address)); } }); return connectedAddresses; diff --git a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts index 935375d6e..ab4b2664e 100644 --- a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts +++ b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts @@ -93,8 +93,7 @@ const sendVideoCallNotification = async ( cta: '', img: '', additionalMeta: { - // type: `${callType}+1`, - type: `${VIDEO_CALL_TYPE.PUSH_VIDEO}+1`, + type: `${callType}+1`, data: JSON.stringify(videoData), }, }, diff --git a/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx b/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx index 514ca5f7e..535924540 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/ChatAndNotification.tsx @@ -164,7 +164,7 @@ export const ChatAndNotification = () => { selectedChat = getDefaultFeedObject({ user: result }); } } - + } setSearchedChats({ [selectedChat.did.toLowerCase() ?? selectedChat.chatId]: selectedChat, @@ -189,7 +189,7 @@ export const ChatAndNotification = () => { setModalOpen(!modalOpen); }; - + useEffect(() => { const modalElement = modalRef.current; @@ -213,7 +213,7 @@ export const ChatAndNotification = () => { modalElement.removeEventListener('wheel', handleScroll); }; }, []); - + return ( { className='modal' overflow="hidden" ref={modalRef} - + // onMouseEnter={() => toggleOverflow('hidden')} // onMouseLeave={() => toggleOverflow('auto')} > diff --git a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/chatSidebar/RequestsFeedList.tsx b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/chatSidebar/RequestsFeedList.tsx index b92f8e4a0..f337fdde2 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/chatSidebar/RequestsFeedList.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/chatSidebar/RequestsFeedList.tsx @@ -76,7 +76,7 @@ export const RequestsFeedList = () => { justifyContent="start" width='100%' flexDirection="column" - + > {(!loading || paginateLoading) && Object.keys(requestsFeed || {}).length ? ( diff --git a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/InboxNotificationFeedList.tsx b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/InboxNotificationFeedList.tsx index 00477c653..7a24fc805 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/InboxNotificationFeedList.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/InboxNotificationFeedList.tsx @@ -38,7 +38,7 @@ export const InboxNotificationFeedList = () => { const isInViewport1 = useIsInViewport(pageRef, '1px'); const { fetchNotification, loading } = useFetchNotification(); - + const fetchSpamNotificationList = async () => { const feeds: NotificationFeedsType | undefined = await fetchNotification({ page: 1, @@ -92,7 +92,7 @@ export const InboxNotificationFeedList = () => { }, [fetchNotification, env, page, account]); useEffect(() => { - + if ( !isInViewport1 || loading || finishedFetchingInbox @@ -114,10 +114,10 @@ export const InboxNotificationFeedList = () => { } try { setPaginateLoading(true); - const feeds = await fetchNotification({ page, limit: notificationLimit }); - if(!Object.keys(feeds || {}).length) setFinishedFetchingInbox(true); + const feeds = await fetchNotification({ page, limit: notificationLimit }); + if(!Object.keys(feeds || {}).length) setFinishedFetchingInbox(true); const newFeed:NotificationFeedsType = {...inboxNotifsFeed,...feeds}; - + setInboxNotifsFeed(newFeed); } catch (error) { console.log(error); diff --git a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/SpamNotificationFeedList.tsx b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/SpamNotificationFeedList.tsx index 7f28c9d44..de6c554fa 100644 --- a/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/SpamNotificationFeedList.tsx +++ b/packages/uiweb/src/lib/components/chatAndNotification/modal/sidebar/notificationSidebar/SpamNotificationFeedList.tsx @@ -72,9 +72,9 @@ export const SpamNotificationFeedList = () => { limit: notificationLimit, spam: true, }); - if(!Object.keys(feeds || {}).length) setFinishedFetchingSpam(true); + if(!Object.keys(feeds || {}).length) setFinishedFetchingSpam(true); const newFeed: NotificationFeedsType = { ...spamNotifsFeed, ...feeds }; - + setSpamNotifsFeed(newFeed); } catch (error) { @@ -91,7 +91,7 @@ export const SpamNotificationFeedList = () => { justifyContent="start" flexDirection="column" width="100%" - + padding="0 3px" > {(!loading || paginateLoading) && diff --git a/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBanner.tsx b/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBanner.tsx index 7bc1f19ff..9dd2c647a 100644 --- a/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBanner.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBanner.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import styled from 'styled-components'; +import React, { useEffect } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { SpaceBannerLoadingSkeleton } from './SpaceBannerLoadingSkeleton'; @@ -11,15 +11,19 @@ import { ParticipantContainer } from '../reusables/ParticipantContainer'; import { HostPfpContainer } from '../reusables'; import live from './../../../icons/live.svg'; -import scheduled from './../../../icons/scheduled.svg'; -import { useGetSpaceInfo } from './../../../hooks'; +import { Scheduled } from '../../../icons/scheduled'; +import { + useGetSpaceInfo, + usePushSpaceSocket, + useSpaceData, +} from './../../../hooks'; export interface ISpaceBannerProps { spaceId: string; orientation?: 'maximized' | 'minimized' | 'pill'; isInvite?: boolean; onBannerClick?: (arg: string) => void; - onJoin?: any; + actionCallback?: any; } /** @@ -38,11 +42,22 @@ export const SpaceBanner: React.FC = ({ orientation, isInvite, onBannerClick, - onJoin, + actionCallback, }) => { const theme = React.useContext(ThemeContext); const spaceData = useGetSpaceInfo(spaceId); + const { + spacesObjectRef, + spaceObjectData, + initSpaceObject, + setSpaceWidgetId, + isSpeaker, + isListener, + account, + env, + } = useSpaceData(); + const spaceStatus = getSpaceStatus(spaceData?.status); const handleClick = () => { @@ -51,77 +66,76 @@ export const SpaceBanner: React.FC = ({ } }; + const handleJoinSpace = async () => { + await initSpaceObject(spaceData?.spaceId as string); + actionCallback(); + setSpaceWidgetId(spaceData?.spaceId as string); + }; + + usePushSpaceSocket({ account, env }); + // Check if the spaceData is not available, show the skeleton loading effect if (!spaceData) { return ; } return ( - - {orientation === 'maximized' && ( - - )} - {orientation === 'maximized' ? null : ( - - )} - - {orientation === 'pill' - ? `${spaceData?.spaceName.slice(0, 20)}...` - : spaceData?.spaceName} - - - - - - {isInvite === true && spaceStatus === 'Live' ? ( - Join this space - ) : isInvite === true && - spaceStatus === 'Scheduled' ? ( - Remind Me - ) : null} - + + {isInvite === true && spaceStatus === 'Live' ? ( + + Join this space + + ) : isInvite === true && spaceStatus === 'Scheduled' ? ( + Remind Me + ) : null} + + ); }; @@ -163,14 +177,17 @@ const Container = styled.div` : props.orientation === 'minimized' ? '12px' : '24px'}; - color: ${(props) => (props.status === 'Live' ? '#f5f5f5' : '#1E1E1E')}; + color: ${(props) => + props.status === 'Live' + ? `${props.theme.titleTextColor}` + : `${props.theme.textColorPrimary}`}; min-width: 0; text-overflow: ellipsis; overflow: hidden; - cursor: ${props => props.clickable && 'pointer'}; + cursor: ${(props) => props.clickable && 'pointer'}; `; -const Title = styled.div<{ orientation?: string }>` +const Title = styled.div` display: flex; flex-direction: row; justify-content: flex-start; @@ -185,6 +202,10 @@ const Title = styled.div<{ orientation?: string }>` ? '16px' : '12px'}; line-height: 130%; + color: ${(props) => + props.status === 'Live' + ? props.theme.titleTextColor + : props.theme.textColorPrimary}; width: 90%; line-clamp: ${(props) => (props.orientation === 'maximized' ? '3' : '2')}; @@ -202,7 +223,7 @@ const Status = styled.div` align-items: center; `; -const Time = styled.div<{ orientation?: string }>` +const Time = styled.div` display: ${(props) => (props.orientation === 'maximized' ? 'flex' : 'none')}; flex-direction: row; justify-content: center; @@ -216,23 +237,31 @@ const Icon = styled.img` align-self: center; `; -const TimeText = styled.div<{ status?: string }>` +const TimeText = styled.div` font-weight: 500; font-size: 14px; line-height: 150%; - color: ${(props) => (props.status === 'Live' ? '#fff' : '#71717A')}; + color: ${(props) => + props.status === 'Live' + ? `${props.theme.titleTextColor}` + : `${props.theme.textColorSecondary}`}; `; -const InviteButton = styled.button<{ status?: string }>` +const InviteButton = styled.button` display: flex; justify-content: center; align-items: center; height: 36px; width: 100%; - color: ${(props) => (props.status === 'Live' ? '#FFF' : '#8B5CF6')}; + color: ${(props) => + props.status === 'Live' + ? `${props.theme.titleTextColor}` + : `${props.theme.btnColorPrimary}`}; border-radius: 8px; border: ${(props) => - props.status === 'Live' ? '1px solid #FFF' : '1px solid #8B5CF6'}; + props.status === 'Live' + ? `1px solid ${props.theme.titleTextColor}` + : `1px solid ${props.theme.btnColorPrimary}`}; background: transparent; cursor: pointer; `; diff --git a/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBannerLoadingSkeleton.tsx b/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBannerLoadingSkeleton.tsx index 013d43705..f52e29e9f 100644 --- a/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBannerLoadingSkeleton.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceBanner/SpaceBannerLoadingSkeleton.tsx @@ -33,6 +33,7 @@ const SkeletonContainer = styled.div` border-radius: 17px; border: 1px solid lightgrey; position: relative; + width: inherit; &:after { content: ''; diff --git a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWButton/SCWButton.tsx b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWButton/SCWButton.tsx index ee1244992..d7d105731 100644 --- a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWButton/SCWButton.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWButton/SCWButton.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { ISpacesTheme } from '../../theme'; import { ThemeContext } from '../../theme/ThemeProvider'; +import { SpacesLogo } from '../../../../icons/SpacesLogo'; export interface ISCWButtonProps { // Space Creation Widget Button Interface btnText?: string; @@ -14,7 +15,7 @@ export interface ISCWButtonProps { // Space Creation Widget Button Interface const defaultProps: ISCWButtonProps = { btnText: 'Create your Space', customStyle: { - padding: '20px', + padding: '14px 20px', borderRadius: '12px', border: '0px solid transparent', fontSize: '1rem', @@ -23,7 +24,7 @@ const defaultProps: ISCWButtonProps = { export const SCWButton: React.FC = (props) => { const { btnText, customStyle, onCreate } = props; - + const theme = useContext(ThemeContext); return ( @@ -33,7 +34,10 @@ export const SCWButton: React.FC = (props) => { theme={theme} onClick={onCreate} > - {btnText} + + + {btnText} + ) @@ -46,12 +50,21 @@ const CreateButton = styled.button` border: ${props => props.customStyle.border}; font-size: ${props => props.customStyle.fontSize}; - background-image: ${(props) => props.theme.titleBg}; + background: ${(props) => props.theme.btnColorPrimary}; color: ${(props) => props.theme.titleTextColor}; + display: flex; + align-items: center; + + font-family: 'Strawford'; + cursor: pointer; `; +const BtnText = styled.div` + margin-left: 6px; +`; + SCWButton.defaultProps = defaultProps; export default SCWButton; diff --git a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWCreateModal/SCWCreateModal.tsx b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWCreateModal/SCWCreateModal.tsx index 5054e6235..572a236b3 100644 --- a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWCreateModal/SCWCreateModal.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWCreateModal/SCWCreateModal.tsx @@ -1,5 +1,5 @@ -import React, { MouseEventHandler } from 'react' -import styled from 'styled-components' +import React, { MouseEventHandler, useContext } from 'react' +import styled, { ThemeProvider } from 'styled-components' import { Modal } from '../../reusables/Modal' import { Button } from '../../reusables/Button'; @@ -7,6 +7,7 @@ import { ModalHeader } from '../../reusables/ModalHeader'; import { TextInputWithCounter } from '../../reusables/TextInput'; import { CalendarPurple } from '../../../../icons/CalendarPurple'; +import { ThemeContext } from '../../theme/ThemeProvider'; export interface ISCWCModalProps { // Space Creation Widget Create Modal Interface isInviteVisible?: any; @@ -21,7 +22,8 @@ export interface ISCWCModalProps { // Space Creation Widget Create Modal Interfa } export const SCWCreateModal: React.FC = (props) => { - const { + const theme = useContext(ThemeContext); + const { isInviteVisible, closeCreateModal, handleNameChange, handleDescriptionChange, nameValue, descriptionValue, isDescriptionEnabled, isScheduleVisible, onClose, @@ -29,10 +31,11 @@ export const SCWCreateModal: React.FC = (props) => { const secBtn = { background: 'transparent', - borderColor: '#8b5cf6' + borderColor: theme.btnOutline } return ( + @@ -74,11 +77,12 @@ export const SCWCreateModal: React.FC = (props) => { customStyle={secBtn} onClick={isScheduleVisible} > - + + ) } @@ -87,4 +91,4 @@ const ButtonContainer = styled.div` display: flex; justify-content: space-between; width: 100%; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWInviteModal/SCWInviteModal.tsx b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWInviteModal/SCWInviteModal.tsx index 94c8ac684..1f81b2682 100644 --- a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWInviteModal/SCWInviteModal.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SCWInviteModal/SCWInviteModal.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-prototype-builtins */ -import React, { useState, MouseEventHandler, useContext, useEffect } from 'react' -import styled from 'styled-components' +import React, { useState, MouseEventHandler, useContext } from 'react' +import styled, { ThemeProvider } from 'styled-components' import * as PushAPI from '@pushprotocol/restapi'; import { ModalHeader } from '../../reusables/ModalHeader'; @@ -10,14 +10,22 @@ import { SearchInput } from '../../reusables/SearchInput'; import { ProfileContainer } from '../../reusables/ProfileContainer'; import { ThemeContext } from '../../theme/ThemeProvider'; import { Spinner } from '../../reusables/Spinner'; +import { createIcon } from '../../helpers/blockies'; import CircularProgressSpinner from '../../../loader/loader'; import { useSpaceData } from '../../../../hooks'; import SettingsIcon from '../../../../icons/settingsBlack.svg'; +import { SettingsLogo } from '../../../../icons/SettingsLogo'; import { Image } from '../../../../config'; -import { createIcon } from '../../helpers/blockies'; + +export interface ICustomSearchResult { + account: string; + name?: string; + handle?: string; + image?: string; // dataURL as string +} export interface ISCWIModalProps { // Space Creation Widget Create Modal Interface closeInviteModal?: MouseEventHandler; @@ -32,12 +40,8 @@ export interface ISCWIModalProps { // Space Creation Widget Create Modal Interfa setAdminsList?: any; adminsAddressList?: any; setAdminsAddressList?: any; - onClose: () => void; -} - -interface User { - handle: string; - name: string; + onClose: any; + btnString?: string; } export const SCWInviteModal: React.FC = (props) => { @@ -51,17 +55,18 @@ export const SCWInviteModal: React.FC = (props) => { setAdminsList, adminsAddressList, setAdminsAddressList, - onClose + onClose, + btnString, } = props; const theme = useContext(ThemeContext); - const { env, account } = useSpaceData(); + const { env, account, customSearch } = useSpaceData(); const [invitedMember, setInvitedMember] = useState('') const [loadingAccount, setLoadingAccount] = useState(false) const [searchedUser, setSearchedUser]= useState({}); - const [errorMsg, setErrorMsg] = useState(''); + const [errorMsg, setErrorMsg] = useState(''); const searchMember = async (event: any) => { setInvitedMember(event.target.value) @@ -70,7 +75,36 @@ export const SCWInviteModal: React.FC = (props) => { handleError('Cannot add Host to members'); return; } - + + if (customSearch) { + const customUserResponse = customSearch(event.target.value); + + const hasAccount = (obj: any, uniqueKey: string) => { + const keys = Object.keys(obj); + return keys.length < 4 && keys[0] === uniqueKey; + } + + if(hasAccount(customUserResponse, 'account')) { + const icon = createIcon({ + seed: customUserResponse.account, + size: 10, + scale: 3, + }); + + const searchedUser = { + handle: customUserResponse.account, + name: customUserResponse.account, + image: icon.toDataURL(), + }; + + setSearchedUser(searchedUser) + } else { + setSearchedUser(customUserResponse); + } + + return; + } + try { setLoadingAccount(true); const response = await PushAPI.user.get({ @@ -86,7 +120,7 @@ export const SCWInviteModal: React.FC = (props) => { }); const nullUser = { - walletAddress: event.target.value, + handle: event.target.value, name: event.target.value, image: icon.toDataURL(), }; @@ -131,7 +165,7 @@ export const SCWInviteModal: React.FC = (props) => { setInvitedAddressList([...invitedAddressList, user.did.substring(7)]) setInvitedMembersList([...invitedMembersList, user]); } else { - setInvitedAddressList([...invitedAddressList, user.walletAddress]) + setInvitedAddressList([...invitedAddressList, user.handle]) setInvitedMembersList([...invitedMembersList, user]); } @@ -144,7 +178,7 @@ export const SCWInviteModal: React.FC = (props) => { setAdminsAddressList([...adminsAddressList, user.did.substring(7)]); } else { setAdminsList([...adminsList, user]) - setAdminsAddressList([...adminsAddressList, user.walletAddress]); + setAdminsAddressList([...adminsAddressList, user.handle]); } const updatedArray = invitedMembersList.filter((item: any) => item !== user) @@ -154,7 +188,7 @@ export const SCWInviteModal: React.FC = (props) => { const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.did.substring(7)) setInvitedAddressList(updateAddressArray); } else { - const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.walletAddress) + const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.handle) setInvitedAddressList(updateAddressArray); } @@ -169,7 +203,7 @@ export const SCWInviteModal: React.FC = (props) => { const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.did.substring(7)) setInvitedAddressList(updateAddressArray); } else { - const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.walletAddress) + const updateAddressArray = invitedAddressList.filter((item: string) => item !== user.handle) setInvitedAddressList(updateAddressArray); } }; @@ -182,15 +216,13 @@ export const SCWInviteModal: React.FC = (props) => { const updateAdminAddressArray = adminsAddressList.filter((item: string) => item !== user.did.substring(7)) setAdminsAddressList(updateAdminAddressArray); } else { - const updateAddressArray = adminsAddressList.filter((item: string) => item !== user.walletAddress) + const updateAddressArray = adminsAddressList.filter((item: string) => item !== user.handle) setAdminsAddressList(updateAddressArray); } }; - const tempImageUrl = "https://imgv3.fotor.com/images/blog-richtext-image/10-profile-picture-ideas-to-make-you-stand-out.jpg"; - return ( -
+ @@ -214,11 +246,10 @@ export const SCWInviteModal: React.FC = (props) => { { Object.keys(searchedUser).length === 0 ? null - : searchedUser.hasOwnProperty('walletAddress') ? + : searchedUser.hasOwnProperty('handle') ? Add +} @@ -228,7 +259,6 @@ export const SCWInviteModal: React.FC = (props) => { : Add +} @@ -244,22 +274,17 @@ export const SCWInviteModal: React.FC = (props) => { Invited Members {invitedMembersList.length} { invitedMembersList.map((item: any) => { - if (item.hasOwnProperty('walletAddress')) { + if (item.hasOwnProperty('handle')) { return - Settings icon + } - // btnCallback={() => handleDeleteInvitedUser(item)} removeCallback={() => handleDeleteInvitedUser(item)} promoteCallback={() => handlePromoteToAdmin(item)} border @@ -272,14 +297,9 @@ export const SCWInviteModal: React.FC = (props) => { imageUrl={item.profile.picture} contBtn={ - Settings icon + } - // btnCallback={() => handleDeleteInvitedUser(item)} removeCallback={() => handleDeleteInvitedUser(item)} promoteCallback={() => handlePromoteToAdmin(item)} border @@ -297,22 +317,17 @@ export const SCWInviteModal: React.FC = (props) => { Speakers {adminsList.length} { adminsList.map((item: any) => { - if (item.hasOwnProperty('walletAddress')) { + if (item.hasOwnProperty('handle')) { return - Settings icon + } - // btnCallback={() => handleDeleteInvitedUser(item)} removeCallback={() => handleDeleteInvitedAdmin(item)} // promoteCallback={() => handlePromoteToAdmin(item)} border @@ -325,14 +340,9 @@ export const SCWInviteModal: React.FC = (props) => { imageUrl={item.profile.picture} contBtn={ - Settings icon + } - // btnCallback={() => handleDeleteInvitedUser(item)} removeCallback={() => handleDeleteInvitedAdmin(item)} // promoteCallback={() => handlePromoteToAdmin(item)} border @@ -351,11 +361,11 @@ export const SCWInviteModal: React.FC = (props) => { { isLoading ? - : 'Create Space' + : btnString ?? 'Create Space' } -
+ ) } @@ -406,13 +416,13 @@ const ContBtn = styled.button` line-height: 18px; width: max-content; background: transparent; - color: #8B5CF6; + color: ${props => props.theme.btnColorPrimary}; border-radius: 6px; font-weight: 500; font-size: 12px; padding: 4px 8px; border-radius: 8px; - border: 1px solid #8B5CF6; + border: 1px solid ${props => props.theme.btnOutline}; cursor: pointer; `; @@ -420,4 +430,4 @@ const ErrorMessage = styled.div` color: #E93636; font-size: 14px; margin-bottom: 8px; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SpaceCreationWidget.tsx b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SpaceCreationWidget.tsx index 54bee046c..0ce55df81 100644 --- a/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SpaceCreationWidget.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceCreationWidget/SpaceCreationWidget.tsx @@ -7,14 +7,14 @@ import { SCWScheduleModal } from './SCWScheduleModal/SCWScheduleModal'; import { SCWInviteModal } from './SCWInviteModal/SCWInviteModal'; import { SCWButton } from './SCWButton'; -import { useSpaceData } from '../../../hooks'; +import { useSpaceData, usePushSpaceSocket } from '../../../hooks'; export interface ISpaceCreateWidgetProps { - CustomComponent?: any; + children?: React.ReactNode; } export const SpaceCreationWidget:React.FC = (props) => { - const { CustomComponent } = props; + const { children } = props; const [isCreateModalVisible, setIsCreateModalVisible] = useState(false); const [isScheduleModalVisible, setIsScheduleModalVisible] = useState(false); @@ -35,7 +35,9 @@ export const SpaceCreationWidget:React.FC = (props) => time: Date.now(), }) - const { signer, env, account } = useSpaceData(); + const { signer, env, account, pgpPrivateKey } = useSpaceData(); + + usePushSpaceSocket({ account, env }); const handleNameChange = (event: any) => { setSpaceState((prevState) => ({...prevState, spaceName: event.target.value})) @@ -99,8 +101,8 @@ export const SpaceCreationWidget:React.FC = (props) => time: Date.now(), }) } - - const testCreateSpace = async () => { + + const createSpace = async () => { const spaceCreate = { spaceName: spaceState.spaceName.length === 0 ? `${account}'s Space` : spaceState.spaceName, spaceDescription: 'Push Space', @@ -110,13 +112,13 @@ export const SpaceCreationWidget:React.FC = (props) => isPublic: true, scheduleAt: spaceState.time > Date.now() ? new Date(spaceState.time) : new Date(Date.now() + 120000), signer: signer as PushAPI.SignerType, - env + env, + ...(pgpPrivateKey && pgpPrivateKey !== '' && { pgpPrivateKey }), // Conditionally add pgpPrivateKey } try { setLoading(true); const response = await PushAPI.space.create(spaceCreate); - console.log(response); } catch (e:any) { console.error(e.message); @@ -128,65 +130,65 @@ export const SpaceCreationWidget:React.FC = (props) => }; return ( - - { - CustomComponent - ? - - : - - } - - {isCreateModalVisible && - - } - - {isScheduleModalVisible && - - } - - {isInviteModalVisible && - - } - +
+ + {!children && + + } + + {children &&
{children}
} + + {isCreateModalVisible && + + } + + {isScheduleModalVisible && + + } + + {isInviteModalVisible && + + } +
+
) } const SCWContainer = styled.div` - font-family: 'Strawford'; // update to fontFamily theme -`; \ No newline at end of file + font-family: 'Strawford'; // update to fontFamily theme +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceFeed/SpaceFeed.tsx b/packages/uiweb/src/lib/components/space/SpaceFeed/SpaceFeed.tsx index 1cb55ff6f..efb7035e8 100644 --- a/packages/uiweb/src/lib/components/space/SpaceFeed/SpaceFeed.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceFeed/SpaceFeed.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; +import React, { useContext, useState } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { SpaceIFeeds } from '@pushprotocol/restapi'; @@ -13,22 +13,26 @@ import { useMySpaces, usePopularSpaces, useSpaceRequests, + usePushSpaceSocket, } from '../../../hooks'; import { ISpacePaginationData } from '../../../context/spacesContext'; import spacesIcon from '../../../icons/Spaces.svg'; +import { ThemeContext } from '../theme/ThemeProvider'; enum OrientationEnums { Horizontal = 'horizontal', Vertical = 'vertical', } -enum Tabs { +export enum FeedTabs { ForYou = 'For You', Popular = 'Popular', HostedByYou = 'Hosted by you', } +type TabsValues = keyof typeof FeedTabs; + enum FilterEnums { All = 'All', Live = 'Live', @@ -38,7 +42,7 @@ export interface ISpaceFeedProps { orientation?: 'horizontal' | 'vertical'; height?: number; width?: number; - sortingOrder?: string[]; + sortingOrder?: Array; showTabs?: boolean; filter?: FilterEnums.All | FilterEnums.Live | FilterEnums.Scheduled; showFilter?: boolean; @@ -46,17 +50,18 @@ export interface ISpaceFeedProps { } export const SpaceFeed: React.FC = ({ - orientation = 'veritcal', + orientation = OrientationEnums.Vertical, height, width, - sortingOrder = [Tabs.Popular, Tabs.ForYou, Tabs.HostedByYou], + sortingOrder = ['Popular', 'ForYou', 'HostedByYou'], showTabs = true, filter = FilterEnums.All, showFilter = true, onBannerClickHandler, }) => { - const [tab, setTab] = useState(sortingOrder[0]); + const theme = useContext(ThemeContext); const [filterTab, setFilterTab] = useState(filter); + const { selectedFeedTab, setSelectedFeedTab } = useSpaceData(); const { account, @@ -66,13 +71,12 @@ export const SpaceFeed: React.FC = ({ setPopularSpaces, spaceRequests, setSpaceRequests, + env, } = useSpaceData(); - const listInnerRef = useFeedScroll(mySpaces.apiData?.length); + usePushSpaceSocket({ account, env }); - const handleTabChange = (tab: string) => { - setTab(tab); - }; + const listInnerRef = useFeedScroll(mySpaces.apiData?.length); const handleFilterData = (spacesList: SpaceIFeeds[]) => { if (filterTab === FilterEnums.All) { @@ -91,17 +95,17 @@ export const SpaceFeed: React.FC = ({ }; const handleMySpacesFilter = (spacesList: SpaceIFeeds[]) => { - if (tab === Tabs.HostedByYou) { + if (selectedFeedTab === FeedTabs.HostedByYou) { return spacesList.filter( (space: SpaceIFeeds) => - space.spaceInformation?.spaceCreator.slice(7).toUpperCase() === + space.spaceInformation?.spaceCreator?.toUpperCase() === account?.toUpperCase() ); } - if (tab === Tabs.ForYou) { + if (selectedFeedTab === FeedTabs.ForYou) { return spacesList.filter( (space: SpaceIFeeds) => - space.spaceInformation?.spaceCreator.slice(7).toUpperCase() !== + space.spaceInformation?.spaceCreator?.toUpperCase() !== account?.toUpperCase() ); } else { @@ -147,19 +151,17 @@ export const SpaceFeed: React.FC = ({ }; const loadMoreData = async () => { - if (tab === Tabs.ForYou) { + if (selectedFeedTab === FeedTabs.ForYou) { incrementSpacePage(mySpaces); } - if (tab === Tabs.Popular) { + if (selectedFeedTab === FeedTabs.Popular) { incrementSpacePage(popularSpaces); } - if (tab === Tabs.HostedByYou) { + if (selectedFeedTab === FeedTabs.HostedByYou) { incrementSpacePage(spaceRequests); } }; - console.log(account); - const onScroll = () => { if (listInnerRef.current) { const { scrollTop } = listInnerRef.current; @@ -181,7 +183,14 @@ export const SpaceFeed: React.FC = ({ mySpaceLoading || popularSpaceLoading || spaceRequestsLoading; return ( -
+ +
{orientation === OrientationEnums.Horizontal ? ( {orientation === OrientationEnums.Horizontal @@ -214,108 +223,81 @@ export const SpaceFeed: React.FC = ({ <> - {sortingOrder.map((tabName: string) => { + {sortingOrder.map((tabName: TabsValues) => { return ( handleTabChange(tabName)} + active={selectedFeedTab === FeedTabs[tabName]} + onClick={() => setSelectedFeedTab(FeedTabs[tabName])} > - {tabName} + {FeedTabs[tabName]} ); })} - - setFilterTab(FilterEnums.All)} + + setFilterTab(FilterEnums.All)} + > + All + + setFilterTab(FilterEnums.Live)} + > + Live + + setFilterTab(FilterEnums.Scheduled)} + > + Scheduled + + + - All - - setFilterTab(FilterEnums.Live)} - > - Live - - setFilterTab(FilterEnums.Scheduled)} - > - Scheduled - - - - - {tab === Tabs.ForYou ? ( - - {mySpaces.apiData && - (handleFilterData( - handleMySpacesFilter(mySpaces.apiData as SpaceIFeeds[]) - ).length === 0 ? ( - - - Join a space - - Get started by joining a space - - - ) : ( - handleFilterData( + + {selectedFeedTab === FeedTabs.ForYou ? ( + + {mySpaces.apiData && + (handleFilterData( handleMySpacesFilter(mySpaces.apiData as SpaceIFeeds[]) - ).map((space: SpaceIFeeds) => { - return ( - - ); - }) - ))} - - ) : tab === Tabs.Popular ? ( - - Popular Spaces - {popularSpaces && - handleFilterData( - popularSpaces.apiData as SpaceIFeeds[] - ).map((space: SpaceIFeeds) => { - return ( - - ); - })} - - ) : ( - - {mySpaces.apiData && - (handleFilterData( - handleMySpacesFilter(mySpaces.apiData as SpaceIFeeds[]) - ).length === 0 ? ( - - - Create a space - - Get started by creating a space - - - ) : ( + ).length === 0 ? ( + + + Join a space + + Get started by joining a space + + + ) : ( + handleFilterData( + handleMySpacesFilter( + mySpaces.apiData as SpaceIFeeds[] + ) + ).map((space: SpaceIFeeds) => { + return ( + + ); + }) + ))} + + ) : selectedFeedTab === FeedTabs.Popular ? ( + + {popularSpaces.apiData && handleFilterData( - handleMySpacesFilter(mySpaces.apiData as SpaceIFeeds[]) + popularSpaces.apiData as SpaceIFeeds[] ).map((space: SpaceIFeeds) => { return ( = ({ } /> ); - }) - ))} - - )} - {loading && } - - - - )} -
+ })} + + ) : ( + + {mySpaces.apiData && + (handleFilterData( + handleMySpacesFilter(mySpaces.apiData as SpaceIFeeds[]) + ).length === 0 ? ( + + + Create a space + + Get started by creating a space + + + ) : ( + handleFilterData( + handleMySpacesFilter( + mySpaces.apiData as SpaceIFeeds[] + ) + ).map((space: SpaceIFeeds) => { + return ( + + ); + }) + ))} + + )} + {loading && } + + + + )} +
+ ); }; @@ -344,16 +357,16 @@ const ScrollContainer = styled.div<{ height?: number; width?: number }>` width: ${(props) => (props.width ? `${props.width}px` : 'inherit')}; height: ${(props) => (props.height ? `${props.height}px` : 'auto')}; overflow-y: auto; -}`; +`; const Container = styled.div` display: flex; flex-direction: column; align-items: center; - background: #ffffff; - border: 1px solid #dcdcdf; + background: ${(props) => props.theme.bgColorPrimary}; + border: 1px solid ${(props) => props.theme.borderColor}; border-radius: 12px; padding: 24px 32px; -}`; +`; const Navigation = styled.div<{ showTabs?: boolean; @@ -365,8 +378,9 @@ const Navigation = styled.div<{ justify-content: space-between; align-items: center; width: ${(props) => (props.width ? `${props.width}px` : 'inherit')}; - border-bottom: 1px solid #DCDCDF; + border-bottom: 1px solid ${(props) => props.theme.borderColor}; margin-bottom: ${(props) => (props.showFilter ? '0' : '27px')}; + background: ${(props) => props.theme.bgColorPrimary}; }`; const NavButtonWrapper = styled.div` @@ -374,21 +388,25 @@ const NavButtonWrapper = styled.div` flex-direction: row; justify-content: space-between; align-items: center; -}`; +`; const NavButton = styled.button<{ active?: boolean }>` padding: 10px 30px; font-weight: 450; font-size: 14px; border: none; - border-bottom: ${(props) => (props.active ? '2px solid #8B5CF6' : 'none')}; + border-bottom: ${(props) => + props.active ? `2px solid ${props.theme.btnColorPrimary}` : 'none'}; background: none; - color : ${(props) => (props.active ? '#000000' : '#71717A')}; + color: ${(props) => + props.active + ? `${props.theme.textColorPrimary}` + : `${props.theme.textColorSecondary}`}; &:hover { cursor: pointer; } -}`; +`; const Spaces = styled.div<{ orientation?: string }>` display: flex; @@ -396,23 +414,23 @@ const Spaces = styled.div<{ orientation?: string }>` props.orientation === 'horizontal' ? 'row' : 'column'}; justify-content: flex-start; align-items: center; - background: #ffffff; + background: ${(props) => props.theme.bgColorPrimary}; width: ${(props) => props.orientation === 'horizontal' ? 'inherit' : '100%'}; height: auto; gap: 16px; -}`; +`; const PopularSpaces = styled.div` display: flex; flex-direction: column; justify-content: space-between; align-items: center; - background: #ffffff; - width: 100%; + background: ${(props) => props.theme.bgColorPrimary}; + width: 100%; height: auto; gap: 16px; -}`; +`; const Text = styled.div` width: 100%; @@ -420,17 +438,17 @@ const Text = styled.div` font-family: 'Strawford'; font-weight: 450; font-size: 18px; -}`; +`; const Filter = styled.div<{ showFilter?: boolean }>` display: ${(props) => (props.showFilter ? 'flex' : 'none')}; flex-direction: row; justify-content: flex-start; align-items: center; - background: #ffffff; + background: ${(props) => props.theme.bgColorPrimary}; width: 100%; margin: 22px 0; -}`; +`; const FilterButton = styled.button<{ active: boolean }>` display: inline-flex; @@ -439,16 +457,22 @@ const FilterButton = styled.button<{ active: boolean }>` justify-content: center; align-items: center; border-radius: 99px; - border: 1px solid #C4B5FD; - background: ${(props) => (props.active ? '#8B5CF6' : '#EDE9FE')}; - color: ${(props) => (!props.active ? '#8B5CF6' : '#FFF')}; + border: 1px solid ${(props) => props.theme.borderColor}; + background: ${(props) => + props.active + ? `${props.theme.btnColorPrimary}` + : `${props.theme.bgColorSecondary}`}; + color: ${(props) => + props.active + ? `${props.theme.titleTextColor}` + : `${props.theme.textColorPrimary}`}; margin-right: 8px; font-size: 14px; &:hover { cursor: pointer; } -}`; +`; const NoSpaces = styled.div` display: flex; @@ -456,23 +480,23 @@ const NoSpaces = styled.div` justify-content: center; align-items: center; margin: 130px 0; -}`; +`; const SpacesIcon = styled.img` width: 36px; height: 36px; -}`; +`; const NoSpacesTextV1 = styled.div` font-family: 'Strawford'; font-weight: 450; font-size: 16px; - color: #000; + color: ${(props) => props.theme.textColorPrimary}}; }`; const NoSpacesTextV2 = styled.div` font-family: 'Strawford'; font-weight: 450; - color: #71717A; + color: ${(props) => props.theme.textColorSecondary}}; font-size: 14px; -}`; +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceInvites/SpaceInvites.tsx b/packages/uiweb/src/lib/components/space/SpaceInvites/SpaceInvites.tsx index 6e808f5fa..7ae369c2a 100644 --- a/packages/uiweb/src/lib/components/space/SpaceInvites/SpaceInvites.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceInvites/SpaceInvites.tsx @@ -1,67 +1,43 @@ -import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; +import React, { useContext, useEffect, useState } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { Modal } from '../reusables/Modal'; import { Spinner } from '../reusables/Spinner'; import { ModalHeader } from '../reusables/ModalHeader'; -import { useFeedScroll, useSpaceData, useSpaceRequests } from '../../../hooks'; +import { + useFeedScroll, + useSpaceData, + useSpaceRequests, + usePushSpaceSocket, +} from '../../../hooks'; import { SpaceBanner } from '../SpaceBanner'; +import { ISpacesTheme } from '../theme'; +import { ThemeContext } from '../theme/ThemeProvider'; + export interface ISpaceInvitesProps { children?: React.ReactNode; + actionCallback?: any; + onBannerClickHandler?: (arg: string) => void; } -// temp -let spaceId = ""; +interface IThemeProps { + theme?: ISpacesTheme; +} export const SpaceInvites: React.FC = ({ children, + actionCallback, + onBannerClickHandler, }: ISpaceInvitesProps) => { + const theme = useContext(ThemeContext); const [modalOpen, setModalOpen] = useState(false); const { spaceRequests, setSpaceRequests } = useSpaceData(); const containerRef = useFeedScroll(spaceRequests.apiData?.length); - const [playBackUrl, setPlayBackUrl] = useState(''); - const { - spacesObjectRef, - spaceObjectData, - initSpaceObject, - setSpaceWidgetId, - isSpeaker, - isListener, - account, - } = useSpaceData(); - - const handleJoinSpace = async (space: any) => { - await initSpaceObject(space?.spaceId as string); - - if (isSpeaker) { - // create audio stream - await spacesObjectRef.current.createAudioStream(); - spaceId = space?.spaceId; // temp - } - if (isListener) { - await spacesObjectRef?.current?.join(); - const playBackUrl = spaceObjectData.spaceDescription; - setPlayBackUrl(playBackUrl); - handleCloseModal(); - setSpaceWidgetId(space?.spaceId as string); - console.log('space joined'); - } - }; + const { account, env } = useSpaceData(); - useEffect(() => { - if (!spaceObjectData?.connectionData?.local.stream || !isSpeaker) return; - const joinSpaceAsSpeaker = async () => { - console.log('joining as a speaker'); - await spacesObjectRef?.current?.join(); - setSpaceWidgetId(spaceId); - console.log('space joined'); - handleCloseModal(); - }; - joinSpaceAsSpeaker(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [spaceObjectData?.connectionData?.local.stream]); + usePushSpaceSocket({ account, env }); const handleOpenModal = () => { setModalOpen(true); @@ -71,6 +47,20 @@ export const SpaceInvites: React.FC = ({ setModalOpen(false); }; + const handleCustomClose = () => { + if (actionCallback) { + actionCallback(); + } + + setModalOpen(false); + }; + + const handleClick = (spaceId: string) => { + if (onBannerClickHandler) { + return onBannerClickHandler(spaceId || ''); + } + }; + const loadMoreData = () => { if ( loading === false && @@ -78,7 +68,6 @@ export const SpaceInvites: React.FC = ({ spaceRequests.lastPage && spaceRequests.currentPage < spaceRequests.lastPage ) { - console.log('Load More Data'); setSpaceRequests({ currentPage: spaceRequests.currentPage + 1, lastPage: spaceRequests.lastPage + 1, @@ -97,7 +86,7 @@ export const SpaceInvites: React.FC = ({ const { loading } = useSpaceRequests(account); return ( - <> + {!children && } {children &&
{children}
} @@ -122,7 +111,10 @@ export const SpaceInvites: React.FC = ({ spaceId={space.spaceId} orientation="maximized" isInvite={true} - onJoin={() => handleJoinSpace(space)} + actionCallback={handleCustomClose} + onBannerClick={ + onBannerClickHandler ? handleClick : undefined + } /> ); }) @@ -132,20 +124,20 @@ export const SpaceInvites: React.FC = ({ )} - +
); }; -const Button = styled.button` +const Button = styled.button` padding: 8px 16px; - background-color: #8b5cf6; - color: #fff; + background-color: ${(props) => props.theme.btnColorPrimary}; + color: ${(props) => props.theme.textColorPrimary}; border: none; border-radius: 4px; cursor: pointer; `; -const ScrollContainer = styled.div` +const ScrollContainer = styled.div` max-height: 400px; width: inherit; margin-top: 24px; @@ -161,12 +153,12 @@ const ScrollContainer = styled.div` -webkit-appearance: none; width: 4px; height: auto; - background: #8b5cf6; + background: ${(props) => props.theme.btnColorPrimary}; border-radius: 99px; } `; -const InviteContainer = styled.div` +const InviteContainer = styled.div` display: flex; flex-direction: column; gap: 16px; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/EndWidgetContent.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/EndWidgetContent.tsx new file mode 100644 index 000000000..13785109b --- /dev/null +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/EndWidgetContent.tsx @@ -0,0 +1,66 @@ +import React, { MouseEventHandler } from 'react'; +import { Item, Container, Image, Text } from '../../../config'; +import SpaceEnded from '../../../icons/SpaceEnded.svg'; +import { SpaceInfoText } from './ScheduledWidgetContent'; + +import { ThemeProvider } from 'styled-components'; +import { ThemeContext } from '../theme/ThemeProvider'; + +interface IEndWidgetContentProps { + onClose: MouseEventHandler; + toggleWidgetVisibility: () => void; +} + +export const EndWidgetContent: React.FC = ({ + onClose, + toggleWidgetVisibility, +}) => { + const theme = React.useContext(ThemeContext); + const handleCloseWidget: React.MouseEventHandler = ( + event + ) => { + // Call for hiding the widget + toggleWidgetVisibility(); + + // Call for running onClose handler from prop + onClose(event); + }; + + return ( + + + End Icon + This Space has ended + + + Close + + + + + ); +}; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/LiveSpaceProfileContainer.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/LiveSpaceProfileContainer.tsx index c7cf15281..90791a589 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/LiveSpaceProfileContainer.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/LiveSpaceProfileContainer.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { IMediaStream } from '@pushprotocol/restapi'; import { Image, Item, Text } from '../../../config'; @@ -5,6 +6,9 @@ import HandIcon from '../../../icons/hand.svg'; import MicOffIcon from '../../../icons/micoff.svg'; import { VideoPlayer } from './VideoPlayer'; +import { ThemeProvider } from 'styled-components'; +import { ThemeContext } from '../theme/ThemeProvider'; + export interface ILiveSpaceProfileContainerProps { wallet: string; isHost?: boolean; @@ -18,6 +22,7 @@ export interface ILiveSpaceProfileContainerProps { export const LiveSpaceProfileContainer = ( options: ILiveSpaceProfileContainerProps ) => { + const theme = React.useContext(ThemeContext); const { wallet, isHost, @@ -29,62 +34,64 @@ export const LiveSpaceProfileContainer = ( } = options || {}; return ( - - Profile pic - - {wallet.slice(7, 12).concat('...')} - {stream && } - - {requested ? ( - - - Requested - - Hand Icon - - ) : ( - - - {isHost ? 'Host' : isSpeaker ? 'Speaker' : 'Listener'} - - {!mic && ( + + + Profile pic + + {wallet.replace('eip155:', '').slice(0, -36) + '...'} + {stream && } + + {requested ? ( + + + Requested + Mic Off Icon - )} - - )} - + + ) : ( + + + {isHost ? 'Host' : isSpeaker ? 'Speaker' : 'Listener'} + + {!mic && ( + Mic Off Icon + )} + + )} + + ); }; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/LiveWidgetContent.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/LiveWidgetContent.tsx index 93c056e70..88fa19aff 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/LiveWidgetContent.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/LiveWidgetContent.tsx @@ -1,37 +1,52 @@ -import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; +import React, { useEffect, useState, useRef, useContext } from 'react'; +import styled, { keyframes, ThemeProvider } from 'styled-components'; +import { Player } from '@livepeer/react'; +import * as PushAPI from '@pushprotocol/restapi'; +import { SpaceDTO } from '@pushprotocol/restapi'; import { LiveSpaceProfileContainer } from './LiveSpaceProfileContainer'; import { SpaceMembersSectionModal } from './SpaceMembersSectionModal'; +import { createBlockie } from '../helpers/blockies'; +import { ThemeContext } from '../theme/ThemeProvider'; + +import CircularProgressSpinner from '../../loader/loader'; + import { Button, Image, Item, Text } from '../../../config'; import MicOnIcon from '../../../icons/micon.svg'; import MicEngagedIcon from '../../../icons/MicEngage.svg'; import MuteIcon from '../../../icons/Muted.svg'; import ShareIcon from '../../../icons/Share.svg'; import MembersIcon from '../../../icons/Members.svg'; -import { SpaceDTO } from '@pushprotocol/restapi'; - import { useSpaceData } from '../../../hooks'; -import { Player } from '@livepeer/react'; +import { SpaceStatus } from './WidgetContent'; +import { pCAIP10ToWallet } from '../../../helpers'; interface LiveWidgetContentProps { spaceData?: SpaceDTO; // temp props only for testing demo purpose for now isHost?: boolean; + setSpaceStatusState: React.Dispatch>; } + export const LiveWidgetContent: React.FC = ({ spaceData, isHost, + setSpaceStatusState, }) => { - const tempImageUrl = - 'https://imgv3.fotor.com/images/blog-richtext-image/10-profile-picture-ideas-to-make-you-stand-out.jpg'; const [showMembersModal, setShowMembersModal] = useState(false); - const [isMicOn, setIsMicOn] = useState(true); const [playBackUrl, setPlayBackUrl] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isDDOpen, setIsDDOpen] = useState(false); + + const dropdownRef = useRef(null); + + const theme = useContext(ThemeContext); + const { spacesObjectRef, spaceObjectData, + setSpaceObjectData, isSpeaker, isListener, setSpaceWidgetId, @@ -39,57 +54,116 @@ export const LiveWidgetContent: React.FC = ({ initSpaceObject, } = useSpaceData(); + console.log( + '🚀 ~ file: LiveWidgetContent.tsx:53 ~ spacesObjectRef:', + spacesObjectRef + ); + + const isMicOn = spaceObjectData?.connectionData?.local?.audio; + + const handleMicState = async () => { + await spacesObjectRef?.current?.enableAudio?.({ state: !isMicOn }); + }; + + const handleDDState = () => { + setIsDDOpen(!isDDOpen); + }; + const handleJoinSpace = async () => { if (!spaceData) { return; } + setIsLoading(!isLoading); + await initSpaceObject(spaceData?.spaceId as string); - + // useEffects below will handle the rest + }; + + const handleEndSpace = async () => { + if (!spacesObjectRef?.current) return; + await spacesObjectRef?.current?.stop?.(); + spacesObjectRef.current = null; + setSpaceObjectData?.(PushAPI.space.initSpaceData); + setSpaceStatusState?.(SpaceStatus.Ended); + setIsLoading(false); + }; + + const handleLeaveSpace = async () => { + if (!spacesObjectRef?.current) return; + if (isHost || isSpeaker) { + await spacesObjectRef?.current?.leave?.(); + spacesObjectRef.current = null; + setSpaceObjectData?.(PushAPI.space.initSpaceData); + console.log('Space left'); + } if (isListener) { - console.log('joining as a listner'); - await spacesObjectRef?.current?.join(); - setSpaceWidgetId(spaceData?.spaceId as string); - console.log('space joined'); + spacesObjectRef.current = null; + setSpaceObjectData?.(PushAPI.space.initSpaceData); } + setIsLoading(false); }; - useEffect(()=>{ + // for listener + useEffect(() => { + const JoinAsListner = async () => { + console.log('isListner', isListener); + if ( + isListener && + !isHost && + spaceObjectData.connectionData.local.address + ) { + console.log('joining as a listener'); + await spacesObjectRef?.current?.join?.(); + // setSpaceWidgetId?.(spaceData?.spaceId as string); + setIsLoading(!isLoading); + console.log('space joined'); + } + }; + JoinAsListner(); + }, [isListener]); + + // for speaker + useEffect(() => { const createAudioStream = async () => { - console.log("isSpeaker", isSpeaker); - if (isSpeaker) { + console.log('isSpeaker', isSpeaker); + if (isSpeaker && !spaceObjectData?.connectionData?.local?.stream) { // create audio stream as we'll need it to start the mesh connection console.log('creating audio stream'); - await spacesObjectRef.current.createAudioStream(); + await spacesObjectRef?.current?.createAudioStream?.(); } - } + }; createAudioStream(); - }, [isSpeaker]) + }, [isSpeaker]); + // joining as a speaker useEffect(() => { - if (!spaceObjectData?.connectionData?.local.stream || !isSpeaker) return; + if ( + !spaceObjectData?.connectionData?.local?.stream || + !isSpeaker || + (spaceObjectData?.connectionData?.incoming?.length ?? 0) > 1 + ) + return; + const joinSpaceAsSpeaker = async () => { console.log('joining as a speaker'); - await spacesObjectRef?.current?.join(); - setSpaceWidgetId(spaceData?.spaceId as string); + await spacesObjectRef?.current?.join?.(); + // setSpaceWidgetId?.(spaceData?.spaceId as string); + setIsLoading(!isLoading); console.log('space joined'); }; joinSpaceAsSpeaker(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [spaceObjectData?.connectionData?.local.stream]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [spaceObjectData?.connectionData?.local?.stream]); useEffect(() => { - if (!spaceObjectData.spaceDescription) return; - const playBackUrl = spaceObjectData.spaceDescription; + if (!spaceObjectData?.spaceDescription) return; + const playBackUrl = spaceObjectData?.spaceDescription; setPlayBackUrl(playBackUrl); - }, [spaceObjectData.spaceDescription]); - - // console.log('spaceObjectData', spaceObjectData); - // console.log('playBackUrl', playBackUrl); - // console.log('isListener', isListener); + }, [spaceObjectData?.spaceDescription]); return ( - <> + = ({ overflowY={'auto'} alignContent={'flex-start'} > - {isSpeaker && - spaceObjectData.connectionData.incoming.map((profile) => ( - - ))} + {/* local peer details if speaker or host */} + {(isSpeaker || isHost) && ( + + )} + + {/* remote peer details if speaker or host */} + {(isSpeaker || isHost) && + spaceObjectData?.connectionData?.incoming + ?.slice(1) + .map((profile) => ( + + ))} + + {/* details of everyone in the space if a listner */} {isListener && - spaceObjectData.members.map((profile) => ( - + !isHost && + spaceObjectData?.members.map((profile) => ( +
+ + + {isDDOpen ? ( + + Invite to Speak + + ) : null} +
))}
{isJoined ? ( = ({ alignItems={'center'} gap={'8px'} padding={'10px'} - onClick={() => - isHost || isSpeaker ? setIsMicOn(!isMicOn) : null - } + onClick={() => (isHost || isSpeaker ? handleMicState() : null)} > = ({ } alt="Mic Icon" /> - + {isHost || isSpeaker ? isMicOn ? 'Speaking' @@ -176,24 +294,23 @@ export const LiveWidgetContent: React.FC = ({ alt="Share Icon" /> - {isListener && playBackUrl.length > 0 && ( - + {isListener && !isHost && playBackUrl.length > 0 && ( + + + )} ) : ( @@ -203,13 +320,17 @@ export const LiveWidgetContent: React.FC = ({ border={'none'} borderRadius={'8px'} cursor={'pointer'} - background={ - 'linear-gradient(87.17deg, #EA4EE4 0%, #D23CDF 0.01%, #8B5CF6 100%), linear-gradient(87.17deg, #EA4E93 0%, #DB2777 0.01%, #9963F7 100%), linear-gradient(87.17deg, #B6A0F5 0%, #F46EF7 50.52%, #FFDED3 100%, #FFCFC5 100%), linear-gradient(0deg, #8B5CF6, #8B5CF6), linear-gradient(87.17deg, #B6A0F5 0%, #F46EF7 57.29%, #FF95D5 100%), #FFFFFF' - } + background={`${theme.titleBg}`} onClick={handleJoinSpace} > - - Join this space + + {isLoading ? : 'Join this Space'} )} @@ -219,11 +340,56 @@ export const LiveWidgetContent: React.FC = ({ /> ) : null} - +
); }; -const PeerPlayer = styled(Player)` - width: 0; - height: 0; -}`; +const DropDown = styled.div<{ theme?: any; isDDOpen: any }>` + position: absolute; + top: 0px; + right: 0px; + + display: flex; + flex-direction: column; + gap: 12px; + + justify-content: center; + align-items: start; + + animation: ${({ isDDOpen }) => (isDDOpen ? fadeIn : fadeOut)} 0.2s ease-in-out; + padding: 16px; + background: ${(props) => props.theme.bgColorPrimary}; + color: ${(props) => props.theme.textColorPrimary}; + border-radius: 16px; + + border: 1px solid ${(props) => props.theme.borderColor}; +`; + +const DDItem = styled.div` + cursor: pointer; +`; + +const fadeIn = keyframes` + from { + opacity: 0; + } + to { + opacity: 1; + } +`; + +const fadeOut = keyframes` + from { + opacity: 1; + } + to { + opacity: 0; + visibility: hidden; + } +`; + +const PeerPlayerDiv = styled.div` + visibility: hidden; + position: absolute; + border: 5px solid red; +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/ScheduledWidgetContent.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/ScheduledWidgetContent.tsx index 8b041f9c3..19cfa3d12 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/ScheduledWidgetContent.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/ScheduledWidgetContent.tsx @@ -1,52 +1,70 @@ -import styled from 'styled-components'; +import React, { useEffect, useState } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { Button, Container, Image, Item, Text } from '../../../config'; import { formatDate } from '../../../helpers'; +import CircularProgressSpinner from '../../loader/loader'; import SpacesIcon from '../../../icons/Spaces.svg'; import TwitterIcon from '../../../icons/twitterVector.svg'; import CopyIcon from '../../../icons/copyVector.svg'; -import AtIcon from '../../../icons/atVector.svg'; +import LensterIcon from '../../../icons/lensterVector.svg'; import { SpaceDTO } from '@pushprotocol/restapi'; import { useSpaceData } from '../../../hooks'; -import { useEffect, useState } from 'react'; +import { generateLensterShareURL } from '../helpers/share'; +import { ShareConfig } from '../exportedTypes'; +import { SpaceStatus } from './WidgetContent'; + +import { ThemeContext } from '../theme/ThemeProvider'; + +enum ShareOptions { + Twitter = 'Twitter', + Lenster = 'Lenster', + CopyShareUrl = 'Copy Link', +} + +export type ShareOptionsValues = keyof typeof ShareOptions; interface ScheduledWidgetContentProps { - account?: string; spaceData?: SpaceDTO; - shareUrl?: string; + share?: ShareConfig; // temp props only for testing demo purpose for now isHost?: boolean; isTimeToStartSpace?: boolean; isMember?: boolean; - isSpaceLive: boolean; - setIsSpaceLive: React.Dispatch>; + spaceStatusState: any; + setSpaceStatusState: React.Dispatch>; } export const ScheduledWidgetContent: React.FC = ({ - account, spaceData, - shareUrl, + share, isHost, isMember, - isSpaceLive, - setIsSpaceLive, + spaceStatusState, + setSpaceStatusState, }: ScheduledWidgetContentProps) => { + const theme = React.useContext(ThemeContext); + const { spacesObjectRef, initSpaceObject, spaceObjectData } = useSpaceData(); + const isTimeToStartSpace = true; - const { - spacesObjectRef, - initSpaceObject, - spaceObjectData, - } = useSpaceData(); + const [isStarted, setIsStarted] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const { shareUrl, shareOptions = ['Twitter', 'Lenster', 'CopyShareUrl'] } = + share || {}; const handleStartSpace = async () => { + setIsLoading(!isLoading); + console.log('initializing space object'); - await initSpaceObject(spaceData?.spaceId as string); + await initSpaceObject?.(spaceData?.spaceId as string); console.log('creating audio stream'); - await spacesObjectRef.current.createAudioStream(); + await spacesObjectRef?.current?.createAudioStream?.(); + setIsLoading(!isLoading); setIsStarted(true); console.log('Space Started'); }; @@ -63,6 +81,19 @@ export const ScheduledWidgetContent: React.FC = ({ window.open(tweetUrl, '_blank'); }; + const handleShareLenster = () => { + if (!shareUrl) return; + const url = shareUrl; + const lensterShareText = 'Join this space'; + + const lensterShareUrl = generateLensterShareURL({ + text: lensterShareText, + url, + }); + + window.open(lensterShareUrl, '_blank'); + }; + const handleCopyLink = async () => { try { if (!shareUrl) return; @@ -75,144 +106,165 @@ export const ScheduledWidgetContent: React.FC = ({ } }; + const handleShareAction = (shareOption: ShareOptionsValues) => { + switch (shareOption) { + case ShareOptions.Twitter: + handleShareTweet(); + break; + case ShareOptions.Lenster: + handleShareLenster(); + break; + default: + handleCopyLink(); + break; + } + }; + + const getShareOptionDetails = (shareOption: ShareOptionsValues) => { + let icon = ''; + let alt = ''; + + switch (shareOption) { + case ShareOptions.Twitter: + icon = TwitterIcon; + alt = 'Twitter Icon'; + break; + case ShareOptions.Lenster: + icon = LensterIcon; + alt = 'Lenster Icon'; + break; + default: + icon = CopyIcon; + alt = 'Copy Icon'; + break; + } + + return { icon, alt }; + }; + useEffect(() => { async function startSpace() { - if(isSpaceLive) return; - if (!spaceObjectData?.connectionData?.local.stream || !isStarted) return; - await spacesObjectRef.current.start({ - livepeerApiKey: '2638ace1-0a3a-4853-b600-016e6125b9bc', + if (spaceStatusState === SpaceStatus.Live) return; + if (!spaceObjectData?.connectionData?.local?.stream || !isStarted) return; + await spacesObjectRef?.current?.start?.({ + livepeerApiKey: '6d29b32d-78d4-4a5c-9848-a4a0669eb530', }); setIsStarted(false); - setIsSpaceLive && setIsSpaceLive(true); + setSpaceStatusState && setSpaceStatusState(SpaceStatus.Live); } startSpace(); }, [isStarted]); - console.log('Rendering ScheduledWidgetContent'); - console.log('isStarted?', isStarted); - return ( - - Spaces Icon - {isHost ? ( - isTimeToStartSpace ? ( - It’s time to start your space + + + Spaces Icon + {isHost ? ( + isTimeToStartSpace ? ( + It’s time to start your space + ) : ( + + Your space is scheduled.
Share and let people know when to + join! +
+ ) ) : ( - Your space is scheduled.
Share and let people know when to - join! + This space will go live on{' '} + {formatDate((spaceData?.scheduleAt as any) || new Date())}
- ) - ) : ( - - This space will go live on{' '} - {formatDate((spaceData?.scheduleAt as any) || new Date())} - - )} - {isHost && isTimeToStartSpace && ( - - )} - {!isHost && !isMember && ( - - )} - {!isHost && isMember && ( - - )} - {(!isHost || (isHost && !isTimeToStartSpace)) && shareUrl && ( - - - - Twitter Icon - - - Twitter + )} + {isHost && isTimeToStartSpace && ( + + )} + {!isHost && !isMember && ( + + )} + {!isHost && isMember && ( + + )} + {(!isHost || (isHost && !isTimeToStartSpace)) && shareUrl && ( + + {shareOptions.map((shareOption) => { + const { icon, alt } = getShareOptionDetails(shareOption); + return ( + + handleShareAction(shareOption)} + > + {alt} + + + {ShareOptions[shareOption]} + + + ); + })} + + )} +
+
); }; -const SpaceInfoText = styled.span` +export const SpaceInfoText = styled.span` font-size: 18px; font-weight: 600; text-align: center; + color: ${({ theme }) => theme.textColorPrimary}; `; const ShareLinkItem = styled.div` @@ -223,7 +275,7 @@ const ShareLinkItem = styled.div` `; const ShareLinkButton = styled.button` - background: #e4e4e7; + background: ${({ theme }) => theme.bgColorSecondary}; border-radius: 14px; padding: 16px; border: none; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/SpaceWidget.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/SpaceWidget.tsx index a93c9054a..12edd0a70 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/SpaceWidget.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/SpaceWidget.tsx @@ -1,5 +1,5 @@ -import React, { MouseEventHandler, useEffect, useState } from 'react'; -import styled from 'styled-components'; +import React, { MouseEventHandler, useEffect, useState, useRef } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { SpaceDTO } from '@pushprotocol/restapi'; import * as PushAPI from '@pushprotocol/restapi'; @@ -7,9 +7,11 @@ import { WidgetContent } from './WidgetContent'; import { WidgetHeader } from './WidgetHeader'; import { ISpaceWidgetProps } from '../exportedTypes'; -import { isLiveSpace, isHostOfSpace, isMemberOfSpace } from './helpers/utils'; +import { isHostOfSpace, isMemberOfSpace } from './helpers/utils'; -import { useSpaceData } from '../../../hooks'; +import { usePushSpaceSocket, useSpaceData } from '../../../hooks'; + +import { ThemeContext } from '../theme/ThemeProvider'; const DEFAULT_OFFSET = 16; const DEFAULT_MAXWIDTH = 415; @@ -23,27 +25,31 @@ export const SpaceWidget: React.FC = ( width, zIndex = 1000, spaceId, - shareUrl, + share, onClose = (() => { /** */ }) as MouseEventHandler, isTimeToStartSpace, } = options || {}; - const [widgetHidden, setWidgetHidden] = useState(!spaceId); - const { account, spaceObjectData, initSpaceObject, env } = useSpaceData(); + const spaceStatusRef = useRef(); + + const [widgetHidden, setWidgetHidden] = useState(!spaceId); const [isMinimized, setIsMinimized] = useState(false); - const { getSpaceInfo, setSpaceInfo } = useSpaceData(); const [spaceData, setSpaceData] = useState(); - const isLive = spaceData && spaceData?.status === 'ACTIVE' ? true : false; - // console.log('isLiveInWidget', isLive) + const { getSpaceInfo, setSpaceInfo, account, env, spaceInfo } = + useSpaceData(); + + usePushSpaceSocket({ account, env }); useEffect(() => { if (!spaceId) { return; } + setWidgetHidden(!spaceId); + const fetchData = async () => { try { if (getSpaceInfo(spaceId)) { @@ -59,7 +65,13 @@ export const SpaceWidget: React.FC = ( }; fetchData(); - }, [spaceId]); + }, [env, getSpaceInfo, setSpaceInfo, spaceId]); + + useEffect(() => { + if (spaceId && spaceInfo[spaceId]) { + spaceStatusRef.current = spaceInfo[spaceId].status; + } + }, [spaceId, spaceInfo]); // To Be Implemented Later via Meta messages. // useEffect(() => { @@ -84,33 +96,37 @@ export const SpaceWidget: React.FC = ( // Implement the SpaceWidget component return ( - + + + ); }; @@ -125,14 +141,14 @@ interface WidgetContainerProps { const Container = styled.div` font-family: 'Strawford'; // update to fontFamily theme border-radius: 12px; // update acc to theme - border: 1px solid #dcdcdf; // update acc to theme + border: 1px solid ${(props) => props.theme.borderColor}; // update acc to theme display: flex; flex-direction: column; width: ${(props) => (props.width ? `${props.width}px` : 'auto')}; max-width: ${(props) => props.width ? `${props.width}px` : `${DEFAULT_MAXWIDTH}px`}; min-width: 320px; - background: white; + background: ${(props) => props.theme.bgColorPrimary}; justify-content: flex-start; position: fixed; bottom: ${(props) => props.bottomOffset}px; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/SpacesInfo.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/SpacesInfo.tsx index 15a363597..1cddf6790 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/SpacesInfo.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/SpacesInfo.tsx @@ -1,104 +1,163 @@ -import React, { useContext, MouseEventHandler } from 'react' +import React, { useContext, MouseEventHandler, useState } from 'react' import styled from 'styled-components'; +import * as PushAPI from '@pushprotocol/restapi'; import { Modal } from '../reusables/Modal' import { ModalHeader } from '../reusables/ModalHeader' -import { IThemeProviderProps, ThemeContext } from '../theme/ThemeProvider'; +import { ThemeContext } from '../theme/ThemeProvider'; import { Button } from '../reusables/Button'; import { ProfileContainer } from '../reusables/ProfileContainer'; import Accordion from '../reusables/Accordion'; +import { SCWInviteModal } from '../SpaceCreationWidget/SCWInviteModal'; + +import { useSpaceData } from '../../../hooks'; export interface ISpacesInfoProps { closeSpacesInfo: MouseEventHandler; -} - -interface IThemeProps { - theme: IThemeProviderProps; + spaceData: any; } export const SpacesInfo: React.FC = (props) => { - const tempImageUrl = "https://imgv3.fotor.com/images/blog-richtext-image/10-profile-picture-ideas-to-make-you-stand-out.jpg"; + const { spaceData } = props; const theme = useContext(ThemeContext); + const [isInviteVisible, setIsInviteVisible] = useState(false); + + const [invitedMembersList, setInvitedMembersList] = useState([]) + const [invitedAddressList, setInvitedAddressList] = useState([]) + + const [adminsList, setAdminsList] = useState([]) + const [adminsAddressList, setAdminsAddressList] = useState([]) + + const [isLoading, setLoading] = useState(false); + + const { signer, env, pgpPrivateKey } = useSpaceData(); + const customStyle = { - color: theme.textColorPrimary, - background: theme.bgColorPrimary, - borderColor: theme.borderColor, + color: theme?.textColorPrimary, + background: theme?.bgColorPrimary, + borderColor: theme?.borderColor, fontWeight: '500', padding: '14px', } - const TEMP_MEMBERS = [ - { - handle: 's4m4', - name: 'Samarendra' - }, - { - handle: 'aamsa', - name: 'Aam Saltman' - }, - { - handle: 's4m4', - name: 'Samarendra' - }, - { - handle: 'aamsa', - name: 'Aam Saltman' - }, - ] + const showExplicitInvite: React.MouseEventHandler = () => { + setIsInviteVisible(!isInviteVisible); + } + + const closeInviteModal = () => { + setIsInviteVisible(false); + } + + const adminsArray = spaceData?.members?.filter((member: { isSpeaker: boolean; }) => member.isSpeaker); + + const updateSpace = async () => { + const spaceUpdate = { + spaceName: spaceData?.spaceName, + spaceDescription: 'Push Space', + listeners: invitedAddressList, + spaceImage: 'asd', + speakers: adminsAddressList, + isPublic: true, + scheduleAt: new Date(Date.now() + 120000), + signer: signer as PushAPI.SignerType, + env, + spaceId: spaceData?.spaceId, + status: spaceData?.status, + ...(pgpPrivateKey && pgpPrivateKey !== '' && { pgpPrivateKey }), // Conditionally add pgpPrivateKey + } + + try { + setLoading(true); + const response = await PushAPI.space.update(spaceUpdate); + + console.log(response); + } catch (e:any) { + console.error(e.message); + } finally { + setLoading(false); + closeInviteModal(); + } + }; return ( - + - larryscruff's space - Ac orci quam cras in placerat. Sollicitudin tristique sed nisi proin duis. + {spaceData?.spaceName} + {spaceData?.spaceDescription} - - { - TEMP_MEMBERS.map((item) => { + + {spaceData?.pendingMembers && + spaceData.pendingMembers.map((item: any) => { return + handle={item?.wallet?.substring(7)} + name={item?.wallet?.substring(7)} + imageUrl={item?.image} + /> }) } - - + {adminsArray && + adminsArray.slice(1).map((item: any) => { + return + }) + } + + { + isInviteVisible ? + + : null + } ) @@ -107,7 +166,7 @@ export const SpacesInfo: React.FC = (props) => { /** styling */ const SpacesInfoContainer = styled.div` - color: black; + color: ${(props => props.theme?.textColorPrimary)}; `; const SpacesDetailsContainer = styled.div` @@ -119,6 +178,6 @@ const Title = styled.div` font-weight: 500; `; -const Description = styled.div` - color: ${(props => props.theme.textColorSecondary)}; -`; \ No newline at end of file +const Description = styled.div` + color: ${(props => props.theme?.textColorSecondary)}; +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/VideoPlayer.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/VideoPlayer.tsx index 3156b2065..017643c19 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/VideoPlayer.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/VideoPlayer.tsx @@ -10,16 +10,16 @@ export const VideoPlayer: React.FC = ({ videoCallData }) => { const incomingVideoRef = useRef(null); useEffect(() => { - if (!incomingVideoRef.current) return; + if (!incomingVideoRef?.current) return; const video = incomingVideoRef.current; video.srcObject = videoCallData; video.play(); - }, [incomingVideoRef, videoCallData]); + }, [incomingVideoRef?.current, videoCallData]); return ; }; const Video = styled.video` - height:0; - width:0; -}`; + height: 0; + width: 0; +`; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetContent.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetContent.tsx index a5ee0c810..2e83d2149 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetContent.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetContent.tsx @@ -1,10 +1,12 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, MouseEventHandler } from 'react'; import styled from 'styled-components'; import { LiveWidgetContent } from './LiveWidgetContent'; import { ScheduledWidgetContent } from './ScheduledWidgetContent'; import { SpaceDTO } from '@pushprotocol/restapi'; import { useSpaceData } from '../../../hooks'; +import { EndWidgetContent } from './EndWidgetContent'; +import { ShareConfig } from '../exportedTypes'; const LIVE_WIDGET_CONTENT_FIXED_HEIGHT = '485px'; const SCHEDULED_WIDGET_CONTENT_FIXED_HEIGHT = '350px'; @@ -12,55 +14,81 @@ const SCHEDULED_WIDGET_CONTENT_FIXED_HEIGHT = '350px'; interface WidgetContentProps { account?: string; //Temp Prop to Test Host functionality spaceData?: SpaceDTO; - shareUrl?: string; + share?: ShareConfig; isMinimized: boolean; // temp props only for testing demo purpose for now isHost?: boolean; - isLive: boolean; + spaceStatus: any; isTimeToStartSpace?: boolean; isMember?: boolean; + onClose: MouseEventHandler; + toggleWidgetVisibility: () => void; } + +export enum SpaceStatus { + Live = 'ACTIVE', + Scheduled = 'PENDING', + Ended = 'ENDED', +} + export const WidgetContent: React.FC = ({ account, spaceData, - shareUrl, + share, isHost, isTimeToStartSpace, isMember, isMinimized, - isLive, + spaceStatus, + onClose, + toggleWidgetVisibility, }: WidgetContentProps) => { - // const { isLive } = useSpaceData(); - console.log('isLiveInWidgetContent', isLive); - const [isSpaceLive, setIsSpaceLive] = useState(false); - console.log('isSpaceLive', isSpaceLive); + const [spaceStatusState, setSpaceStatusState] = useState( + SpaceStatus.Scheduled + ); - console.log('Rendering WidgetContent'); useEffect(() => { - setIsSpaceLive(isLive); - }, [isLive]); + if (spaceStatus === SpaceStatus.Live) { + setSpaceStatusState(SpaceStatus.Live); + } + if (spaceStatus === SpaceStatus.Scheduled) { + setSpaceStatusState(SpaceStatus.Scheduled); + } + if (spaceStatus === SpaceStatus.Ended) { + setSpaceStatusState(SpaceStatus.Ended); + } + }, [spaceStatus]); return ( - {isSpaceLive ? ( - - ) : ( + {spaceStatusState === SpaceStatus.Live ? ( + + ) : spaceStatusState === SpaceStatus.Scheduled ? ( + ) : ( + )} @@ -71,10 +99,10 @@ export const WidgetContent: React.FC = ({ const Container = styled.div<{ height: string; isMinimized: boolean }>` display: flex; flex-direction: column; - border-bottom: ${(props) => props.theme.border}; + border-bottom: ${(props) => props.theme.borderColor}; height: ${(props) => (props.isMinimized ? '0' : props.height)}; - transition: height 200ms ease-out; + transition: height 300ms ease-out; overflow: hidden; align-items: center; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetHeader.tsx b/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetHeader.tsx index 7a3068ad4..d2ff481f4 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetHeader.tsx +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/WidgetHeader.tsx @@ -1,5 +1,10 @@ -import React, { useState, MouseEventHandler, useContext } from 'react'; -import styled from 'styled-components'; +import React, { + useState, + useEffect, + MouseEventHandler, + useContext, +} from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { Item, Text } from '../../../config'; import { formatDate } from '../../../helpers'; @@ -15,6 +20,8 @@ import { SpacesInfo } from './SpacesInfo'; import { ThemeContext } from '../theme/ThemeProvider'; import { useSpaceData } from '../../../hooks'; +import { SpaceStatus } from './WidgetContent'; + export interface IWidgetHeaderProps { onClose: MouseEventHandler; spaceData?: any; @@ -23,7 +30,7 @@ export interface IWidgetHeaderProps { toggleWidgetVisibility: () => void; // temp props - isLive?: boolean; + spaceStatus?: any; isHost?: boolean; } @@ -34,7 +41,7 @@ export const WidgetHeader: React.FC = ({ setIsMinimized, toggleWidgetVisibility, spaceData, - isLive, + spaceStatus, }: IWidgetHeaderProps) => { const theme = useContext(ThemeContext); // const { isLive } = useSpaceData(); @@ -43,6 +50,8 @@ export const WidgetHeader: React.FC = ({ 'https://imgv3.fotor.com/images/blog-richtext-image/10-profile-picture-ideas-to-make-you-stand-out.jpg'; const [isSpacesInfoVisible, setIsSpacesInfoVisible] = useState(false); + const [isSpaceLive, setIsSpaceLive] = useState(SpaceStatus.Scheduled); + const handleCloseWidget: React.MouseEventHandler = ( event ) => { @@ -62,114 +71,141 @@ export const WidgetHeader: React.FC = ({ setIsSpacesInfoVisible(false); }; + useEffect(() => { + if (spaceStatus === SpaceStatus.Live) { + setIsSpaceLive(SpaceStatus.Live); + } + if (spaceStatus === SpaceStatus.Scheduled) { + setIsSpaceLive(SpaceStatus.Scheduled); + } + if (spaceStatus === SpaceStatus.Ended) { + setIsSpaceLive(SpaceStatus.Ended); + } + }, [spaceStatus]); + return ( - - {!isLive && ( -
- - - - - {isHost && } - - Settings icon - - - setIsMinimized(!isMinimized)} - src={isMinimized ? CaretUpIcon : CaretDownIcon} - alt="Maximize/Minimize icon" + + + {(isSpaceLive === SpaceStatus.Scheduled || + isSpaceLive === SpaceStatus.Ended) && ( +
+ + - - - -
- )} -
- - {spaceData?.spaceName || 'Test Space'} - - {isLive && ( - - - Settings icon - - - setIsMinimized(!isMinimized)} - src={isMinimized ? CaretUpIcon : CaretDownIcon} - alt="Maximize/Minimize icon" - /> + {isHost && } + + Settings icon + + + setIsMinimized(!isMinimized)} + src={isMinimized ? CaretUpIcon : CaretDownIcon} + alt="Maximize/Minimize icon" + /> + + + + +
+ )} +
+ + {spaceData?.spaceName || 'Test Space'} + + {isSpaceLive === SpaceStatus.Live && ( - + + Settings icon + + + setIsMinimized(!isMinimized)} + src={isMinimized ? CaretUpIcon : CaretDownIcon} + alt="Maximize/Minimize icon" + /> + + + + + + )} +
+ {isSpaceLive === SpaceStatus.Scheduled && ( + + Calendar Icon + + {formatDate(spaceData?.scheduleAt || new Date())} )} -
- {!isLive && ( - - Calendar Icon - - {formatDate(spaceData?.scheduleAt || new Date())} - - - )} - {isLive && ( -
- - Calendar Icon - - Live - - - - - + {isSpaceLive === SpaceStatus.Live && ( +
+ + Calendar Icon + + Live + - {/* + + + + + {/* +190 Listeners */} - -
- )} - {isSpacesInfoVisible ? ( - - ) : null} - +
+
+ )} + {isSpacesInfoVisible ? ( + + ) : null} +
+ ); }; @@ -206,7 +242,7 @@ const Button = styled.button<{ padding: ${(props) => props.padding ?? '0px'}; color: ${(props) => props.color ?? 'inherit'}; margin-left: 10px; - background: rgba(255, 255, 255, 0.2); + background: ${(props) => props.theme.btnColorPrimary}}; border-radius: 6px; border: none; cursor: pointer; diff --git a/packages/uiweb/src/lib/components/space/SpaceWidget/helpers/utils.ts b/packages/uiweb/src/lib/components/space/SpaceWidget/helpers/utils.ts index bc518b603..7a7532433 100644 --- a/packages/uiweb/src/lib/components/space/SpaceWidget/helpers/utils.ts +++ b/packages/uiweb/src/lib/components/space/SpaceWidget/helpers/utils.ts @@ -3,13 +3,13 @@ import { getSpaceStatus } from '../../helpers/space'; export const isHostOfSpace = (account: string, spaceData: SpaceDTO) => { return ( - account.toUpperCase() === spaceData?.spaceCreator.slice(7).toUpperCase() + account.toUpperCase() === spaceData?.spaceCreator.toUpperCase() ); }; export const isMemberOfSpace = (account: string, spaceData: SpaceDTO) => { const isMemberArr = spaceData?.members.filter( - (member) => member.wallet.slice(7).toUpperCase() === account.toUpperCase() + (member) => member.wallet.toUpperCase() === account.toUpperCase() ); return isMemberArr?.length > 0; }; diff --git a/packages/uiweb/src/lib/components/space/SpacesUI.tsx b/packages/uiweb/src/lib/components/space/SpacesUI.tsx index 0db6e0989..0d52374ee 100644 --- a/packages/uiweb/src/lib/components/space/SpacesUI.tsx +++ b/packages/uiweb/src/lib/components/space/SpacesUI.tsx @@ -4,7 +4,8 @@ import { ISpaceBannerProps, SpaceBanner } from './SpaceBanner'; import { SpaceWidget } from './SpaceWidget'; import { ISpaceFeedProps, SpaceFeed } from './SpaceFeed'; import { ISpaceInvitesProps, SpaceInvites } from './SpaceInvites'; -import { SpaceCreationWidget } from './SpaceCreationWidget'; +import { ISpaceCreateWidgetProps, SpaceCreationWidget } from './SpaceCreationWidget'; +import { ICustomSearchResult } from './SpaceCreationWidget/SCWInviteModal'; import { SignerType } from '../../types'; import { ENV } from '../../config'; @@ -16,12 +17,14 @@ export class SpacesUI { public signer: SignerType; public pgpPrivateKey: string; public env: ENV; + public customSearch: ICustomSearchResult | undefined; constructor(props: ISpacesUIProps) { this.account = props.account; this.signer = props.signer; this.pgpPrivateKey = props.pgpPrivateKey; this.env = props.env; + this.customSearch = props.customSearch; } SpaceBanner: React.FC = (options: ISpaceBannerProps) => { @@ -40,11 +43,11 @@ export class SpacesUI { useEffect(() => { setSpaceId(spaceId); }, [spaceId, setSpaceId]); - + useEffect(() => { - setSpaceId(spaceWidgetId); - }, [spaceWidgetId, setSpaceId]); - + if (spaceWidgetId) setSpaceId(spaceWidgetId); + }, [spaceWidgetId]); + return ; } @@ -56,8 +59,8 @@ export class SpacesUI { return ; }; - SpaceCreationButtonWidget = () => { - return + SpaceCreationButtonWidget = (options: ISpaceCreateWidgetProps) => { + return } connectToSockets = () => { diff --git a/packages/uiweb/src/lib/components/space/exportedTypes.ts b/packages/uiweb/src/lib/components/space/exportedTypes.ts index 9c4aaf140..e45295ac1 100644 --- a/packages/uiweb/src/lib/components/space/exportedTypes.ts +++ b/packages/uiweb/src/lib/components/space/exportedTypes.ts @@ -1,15 +1,23 @@ import { MouseEventHandler } from "react"; +import { ICustomSearchResult } from "./SpaceCreationWidget/SCWInviteModal"; + import { ENV } from "../../config"; import { SignerType } from "../../types"; +import { ShareOptionsValues } from "./SpaceWidget/ScheduledWidgetContent"; export interface ISpacesUIProps { account: string; signer: SignerType; pgpPrivateKey: string; env: ENV; + customSearch?: ICustomSearchResult | undefined; } +export interface ShareConfig { + shareUrl?: string; + shareOptions?: Array; +} export interface ISpaceWidgetProps { // Add props specific to the SpaceWidget class method account?: string; @@ -18,7 +26,7 @@ export interface ISpaceWidgetProps { zIndex?: number; spaceId?: string; width?: number; - shareUrl?: string; + share?: ShareConfig; onClose?: MouseEventHandler; // props only for testing demo purpose for now diff --git a/packages/uiweb/src/lib/components/space/helpers/account.ts b/packages/uiweb/src/lib/components/space/helpers/account.ts new file mode 100644 index 000000000..916b5bbae --- /dev/null +++ b/packages/uiweb/src/lib/components/space/helpers/account.ts @@ -0,0 +1,16 @@ +import { ENV } from "../../../config"; + +const ACCOUNT_START_TYPE = { + NFT: 'nft', + GENERAL: 'eip155' +} + +export const isNftProfile = (account: string) => { + return account && account.split(':')[0] === ACCOUNT_START_TYPE.NFT; +} + +export const spaceChainId = (account: string, env: ENV): number => { + if (account && isNftProfile(account)) + return Number(account.split(':')[2]); + return env === ENV.PROD ? 1: 5; // Ethereum Mainnet Id +} \ No newline at end of file diff --git a/packages/uiweb/src/lib/components/space/helpers/blockies.ts b/packages/uiweb/src/lib/components/space/helpers/blockies.ts index 7cfb98eb3..4e68911b4 100644 --- a/packages/uiweb/src/lib/components/space/helpers/blockies.ts +++ b/packages/uiweb/src/lib/components/space/helpers/blockies.ts @@ -117,3 +117,13 @@ export function createIcon(opts: Options): HTMLCanvasElement { return canvas; } + +export function createBlockie(account: string): HTMLCanvasElement { + const iconParams = { + seed: account, + size: 10, + scale: 3, + }; + + return createIcon(iconParams); +} diff --git a/packages/uiweb/src/lib/components/space/helpers/share.ts b/packages/uiweb/src/lib/components/space/helpers/share.ts new file mode 100644 index 000000000..8c5686fa0 --- /dev/null +++ b/packages/uiweb/src/lib/components/space/helpers/share.ts @@ -0,0 +1,14 @@ +const LENSTER_URL = 'https://lenster.xyz'; + +export interface ILensterUrlProps { + text: string; + url: string; +} +export const generateLensterShareURL = ({text, url}: ILensterUrlProps): string => { + const encodedText = encodeURIComponent(text); + const encodedURL = encodeURIComponent(url); + + const outputURL = `${LENSTER_URL}/?text=${encodedText}&url=${encodedURL}`; + + return outputURL; +} diff --git a/packages/uiweb/src/lib/components/space/reusables/Accordion.tsx b/packages/uiweb/src/lib/components/space/reusables/Accordion.tsx index d962d5fdd..409880a2c 100644 --- a/packages/uiweb/src/lib/components/space/reusables/Accordion.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/Accordion.tsx @@ -14,7 +14,7 @@ interface IAccordionProps { const Accordion: React.FC = ({ title, items, children }) => { const theme = useContext(ThemeContext) - const [isOpen, setIsOpen] = useState(true); + const [isOpen, setIsOpen] = useState(false); const toggleAccordion = () => { setIsOpen((prevIsOpen) => !prevIsOpen); @@ -86,4 +86,4 @@ const Image = styled.img` height: ${(props: any): string => props.height || '24px'}; width: ${(props: any): string => props.width || '20px'}; align-self: center; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/reusables/DateTimePicker.tsx b/packages/uiweb/src/lib/components/space/reusables/DateTimePicker.tsx index 1938bf21b..9cb11ad76 100644 --- a/packages/uiweb/src/lib/components/space/reusables/DateTimePicker.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/DateTimePicker.tsx @@ -30,7 +30,7 @@ const DateTimePicker: React.FC = (props) => { const getTime = (hours: number, minutes: number, ampm: string, propsDate: Date) => { let totalMinutes = hours * 60 + minutes; - + if (ampm === 'PM' && hours !== 12) { totalMinutes += 12 * 60; } else if (ampm === 'AM' && hours === 12) { @@ -48,7 +48,7 @@ const DateTimePicker: React.FC = (props) => { const hours = parseInt(selectedHours, 10); const minutes = parseInt(selectedMinutes, 10); const ampm = selectedAMPM; - + const newTimeEpoch = getTime(hours, minutes, ampm, propsDate); setTimeHumanReadable(newTimeEpoch); @@ -118,8 +118,7 @@ const Input = styled.input` width: 330px; background: #FFFFFF; - border: 1px solid ${(props => props.theme.btnOutline)}; - box-shadow: -1px -1px 2px ${(props => props.theme.btnOutline)}, 1px 1px 2px ${(props => props.theme.btnOutline)}; + border: 2px solid ${(props => props.theme.btnOutline)}; border-radius: 12px; font-size: 16px; @@ -137,8 +136,7 @@ const Select = styled.select<{ width?: string }>` margin-top: 12px; background: #FFFFFF; - border: 1px solid ${(props => props.theme.btnOutline)}; - box-shadow: -1px -1px 2px ${(props => props.theme.btnOutline)}, 1px 1px 2px ${(props => props.theme.btnOutline)}; + border: 2px solid ${(props => props.theme.btnOutline)}; border-radius: 12px; font-size: 16px; diff --git a/packages/uiweb/src/lib/components/space/reusables/HostPfpContainer.tsx b/packages/uiweb/src/lib/components/space/reusables/HostPfpContainer.tsx index 3c410a760..e205ca4a8 100644 --- a/packages/uiweb/src/lib/components/space/reusables/HostPfpContainer.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/HostPfpContainer.tsx @@ -1,42 +1,61 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { ThemeProvider } from 'styled-components'; +import { ThemeContext } from '../theme/ThemeProvider'; + +import { ISpacesTheme } from '../theme'; export interface IHostPfpContainerProps { name?: string; handle?: string; imageUrl?: string; - statusTheme: "Live" | "Scheduled" | "Ended"; + statusTheme: 'Live' | 'Scheduled' | 'Ended'; + imageHeight?: string; +} + +interface IThemeProps { + theme?: ISpacesTheme; + statusTheme?: string; imageHeight?: string; } export const HostPfpContainer: React.FC = ({ - name = "Host Name", - handle = "Host Handle", - imageUrl = "", + name = 'Host Name', + handle = 'Host Handle', + imageUrl = '', statusTheme, imageHeight, }: IHostPfpContainerProps) => { + const theme = React.useContext(ThemeContext); return ( - - - - - - - {name} - Host - - {handle && - - {/* Fetch the handle from Lenster */}@{handle} - - } - - + + + + + + + + {name} + + Host + + + {handle && ( + + {/* Fetch the handle from Lenster */}@{handle} + + )} + + + ); }; -const ProfileContainer = styled.div` +const ProfileContainer = styled.div` display: flex; flex-direction: row; justify-content: space-between; @@ -48,13 +67,13 @@ const PfpContainer = styled.div` display: flex; `; -const Pfp = styled.img<{ imageHeight?: string }>` - height: ${(props) => (props.imageHeight ?? '32px')}; - width: ${(props) => (props.imageHeight ?? '32px')};; +const Pfp = styled.img` + height: ${(props) => props.imageHeight ?? '32px'}; + width: ${(props) => props.imageHeight ?? '32px'}; border-radius: 50%; `; -const HostContainer = styled.div` +const HostContainer = styled.div` display: flex; flex-direction: column; justify-content: center; @@ -65,7 +84,7 @@ const HostContainer = styled.div` text-overflow: ellipsis; `; -const HostName = styled.div` +const HostName = styled.div` display: flex; flex-direction: row; font-weight: 600; @@ -73,13 +92,17 @@ const HostName = styled.div` width: 100%; `; -const Name = styled.span` +const Name = styled.span` text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + color:color: ${(props) => + props.statusTheme === 'Live' + ? `${props.theme.titleTextColor}` + : `${props.theme.textColorPrimary}`}; `; -const Host = styled.div<{ statusTheme?: string }>` +const Host = styled.div` display: flex; flex-direction: row; align-items: center; @@ -90,16 +113,22 @@ const Host = styled.div<{ statusTheme?: string }>` height: 19px; background: ${(props) => props.statusTheme === 'Live' - ? 'rgba(255, 255, 255, 0.2);' - : 'rgba(139, 92, 246, 0.2)'}; - color: ${(props) => (props.statusTheme === 'Live' ? 'inherit' : '#8B5CF6')}; + ? `${props.theme.btnOutline}` + : `${props.theme.btnOutline}`}; + color: ${(props) => + props.statusTheme === 'Live' + ? 'inherit' + : `${props.theme.bgColorSecondary}`}; border-radius: 6px; font-weight: 500; font-size: 10px; `; -const HostHandle = styled.div<{ statusTheme?: string }>` - color: ${(props) => (props.statusTheme === 'Live' ? '#F5F5F5E5' : '#71717A')}; +const HostHandle = styled.div` + color: ${(props) => + props.statusTheme === 'Live' + ? `${props.theme.titleTextColor}` + : `${props.theme.textColorSecondary}`}; padding: 0; font-weight: 450; font-size: 14px; diff --git a/packages/uiweb/src/lib/components/space/reusables/Modal.tsx b/packages/uiweb/src/lib/components/space/reusables/Modal.tsx index b45a54949..6f2115048 100644 --- a/packages/uiweb/src/lib/components/space/reusables/Modal.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/Modal.tsx @@ -36,7 +36,7 @@ const ClickawayCloseModal = ({ children, clickawayClose, width }: IModalProps) = export const Modal = ({ clickawayClose, children, width }: IModalProps) => { const theme = useContext(ThemeContext) return ( - + {clickawayClose ? ( {children} ) : ( @@ -53,7 +53,7 @@ export const Modal = ({ clickawayClose, children, width }: IModalProps) => { /* styling */ -const ModalOverlay = styled.div` +const ModalOverlay = styled.div` position: fixed; top: 0; left: 0; @@ -61,8 +61,10 @@ const ModalOverlay = styled.div` height: 100%; background-color: rgba(0, 0, 0, 0.4); /* Black with 40% opacity */ display: flex; + color: ${props => props.theme.textColorPrimary ?? '#000'}; justify-content: center; align-items: center; + z-index: 10; `; const ModalParent = styled.div` @@ -80,4 +82,4 @@ const ModalParent = styled.div` border-radius: 12px; width: ${(props => props.width ? props.width : 'auto')}; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/reusables/ModalHeader.tsx b/packages/uiweb/src/lib/components/space/reusables/ModalHeader.tsx index e6df966a6..07ac0d6f6 100644 --- a/packages/uiweb/src/lib/components/space/reusables/ModalHeader.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/ModalHeader.tsx @@ -1,8 +1,9 @@ -import { MouseEventHandler } from 'react'; -import styled from 'styled-components'; +import { MouseEventHandler, useContext } from 'react'; +import styled, { ThemeProvider } from 'styled-components'; import { CloseSvg } from '../../../icons/CloseSvg'; import { ArrowLeft } from '../../../icons/ArrowLeft'; +import { ThemeContext } from '../theme/ThemeProvider'; export interface IModalHeaderProps { heading: string; @@ -12,8 +13,9 @@ export interface IModalHeaderProps { } export const ModalHeader = (props: IModalHeaderProps) => { + const theme = useContext(ThemeContext); return ( -
+
{props.backCallback ? ( @@ -23,7 +25,9 @@ export const ModalHeader = (props: IModalHeaderProps) => { {props.heading} - {props.headingBadgeNumber && {props.headingBadgeNumber}} + {props.headingBadgeNumber && ( + {props.headingBadgeNumber} + )} {props.closeCallback ? ( @@ -32,7 +36,7 @@ export const ModalHeader = (props: IModalHeaderProps) => { ) : null}
-
+ ); }; @@ -43,6 +47,7 @@ const Header = styled.div` width: 100%; margin-bottom: 24px; + color: ${(props) => props.theme.textColorPrimary}; `; const BackBtn = styled.button` @@ -91,7 +96,7 @@ const NumberBadge = styled.div` display: flex; justify-content: center; align-items: center; - background:#8B5CF6; + background: ${(props) => props.theme.btnColorPrimary}; color: #fff; border-radius: 8px; margin-left: 8px; diff --git a/packages/uiweb/src/lib/components/space/reusables/ParticipantContainer.tsx b/packages/uiweb/src/lib/components/space/reusables/ParticipantContainer.tsx index 789f016ce..d65eb73c9 100644 --- a/packages/uiweb/src/lib/components/space/reusables/ParticipantContainer.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/ParticipantContainer.tsx @@ -66,7 +66,7 @@ const ParticipantsIconContainer = styled.div<{ orientation?: string }>` padding: 0 4px; }`; -const ParticipantsIcon = styled.img<{ imageHeight?: any }>` +const ParticipantsIcon = styled.img<{ imageHeight?: any }>` height: ${(props) => (props.imageHeight ? props.imageHeight : '31px')}; border-radius: 50%; @@ -74,19 +74,19 @@ const ParticipantsIcon = styled.img<{ imageHeight?: any }>` position: relative; top: 0; left: 0; - z-index: 3; + // z-index: 3; } &.index1 { position: relative; top: 0; left: -50%; - z-index: 2; + // z-index: 2; } &.index2 { position: relative; top: 0; left: -100%; - z-index: 1; + // z-index: 1; } }`; diff --git a/packages/uiweb/src/lib/components/space/reusables/ProfileContainer.tsx b/packages/uiweb/src/lib/components/space/reusables/ProfileContainer.tsx index 4eed4f884..1c49480bd 100644 --- a/packages/uiweb/src/lib/components/space/reusables/ProfileContainer.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/ProfileContainer.tsx @@ -1,5 +1,5 @@ import React, { useContext, useState, useRef, useEffect } from 'react'; -import styled, { keyframes } from 'styled-components'; +import styled, { keyframes, ThemeProvider } from 'styled-components'; import { ThemeContext } from '../theme/ThemeProvider'; export interface IProfileContainerProps { @@ -43,15 +43,16 @@ export const ProfileContainer: React.FC = ({ setIsDDOpen(false); } }; - + document.addEventListener('mousedown', handleOutsideClick); - + return () => { document.removeEventListener('mousedown', handleOutsideClick); }; }, []); return ( + @@ -90,6 +91,7 @@ export const ProfileContainer: React.FC = ({ : null } + ); }; @@ -104,6 +106,7 @@ const ParentContainer = styled.div<{ border?: boolean }>` padding: 8px 16px; border: ${(props => props.border ? '1px solid #E4E4E7' : 'none')}; + color: ${props => props.theme.textColorPrimary ?? '#000'}; border-radius: 16px; `; @@ -162,7 +165,7 @@ const Host = styled.div` line-height: 18px; width: max-content; background: rgba(139, 92, 246, 0.2); - color: #8B5CF6; + color: ${props => props.theme.btnColorPrimary}; border-radius: 6px; font-weight: 500; font-size: 12px; diff --git a/packages/uiweb/src/lib/components/space/reusables/SearchInput.tsx b/packages/uiweb/src/lib/components/space/reusables/SearchInput.tsx index 5f44c25e2..b0f8539ca 100644 --- a/packages/uiweb/src/lib/components/space/reusables/SearchInput.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/SearchInput.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useContext } from 'react'; -import styled from 'styled-components'; +import styled, { ThemeProvider } from 'styled-components'; import { ISpacesTheme } from '../theme'; import { ThemeContext } from '../theme/ThemeProvider'; @@ -21,6 +21,7 @@ export const SearchInput = (props: ISearchInputProps) => { }; return ( + @@ -32,6 +33,7 @@ export const SearchInput = (props: ISearchInputProps) => { + ); }; @@ -42,7 +44,7 @@ const InputContainer = styled.div` margin: 16px 0; - font-family: 'Strawford'; // update to fontFamily theme + font-family: 'Strawford'; // update to fontFamily theme `; const LabelContainer = styled.div` @@ -50,6 +52,7 @@ const LabelContainer = styled.div` justify-content: space-between; font-weight: 500; + color: ${props => props.theme.textColorPrimary ?? '#000'} `; const Input = styled.input` @@ -59,8 +62,7 @@ const Input = styled.input` width: 330px; background: #FFFFFF; - border: 1px solid ${(props => props.theme.btnOutline)}; - box-shadow: -1px -1px 2px ${(props => props.theme.btnOutline)}, 1px 1px 2px ${(props => props.theme.btnOutline)}; + border: 2px solid ${(props => props.theme.btnOutline)}; border-radius: 12px; `; @@ -73,4 +75,4 @@ const CloseBtn = styled.div` right: 0; top: 0; padding: 1.75rem 0.75rem; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/reusables/Spinner.tsx b/packages/uiweb/src/lib/components/space/reusables/Spinner.tsx index d635de040..0d975ac6f 100644 --- a/packages/uiweb/src/lib/components/space/reusables/Spinner.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/Spinner.tsx @@ -1,6 +1,7 @@ import React, { useContext } from 'react'; -import styled, { keyframes } from 'styled-components'; +import styled, { keyframes, ThemeProvider } from 'styled-components'; import { SpinnerSvg } from '../../../icons/SpinnerSvg'; +import { ThemeContext } from '../theme/ThemeProvider'; type SpinnerPropType = { size?: string; @@ -11,10 +12,13 @@ type SpinLoaderPropType = { }; export const Spinner: React.FC = ({ size = 42 }) => { + const theme = useContext(ThemeContext); return ( - - - + + + + + ); }; diff --git a/packages/uiweb/src/lib/components/space/reusables/TextInput.tsx b/packages/uiweb/src/lib/components/space/reusables/TextInput.tsx index d87d90a2d..509945f4d 100644 --- a/packages/uiweb/src/lib/components/space/reusables/TextInput.tsx +++ b/packages/uiweb/src/lib/components/space/reusables/TextInput.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, useContext } from 'react'; -import styled from 'styled-components'; +import styled, { ThemeProvider } from 'styled-components'; import { ISpacesTheme } from '../theme'; import { ThemeContext } from '../theme/ThemeProvider'; @@ -24,6 +24,7 @@ export const TextInputWithCounter = (props: ITextInputProps) => { }; return ( + @@ -31,6 +32,7 @@ export const TextInputWithCounter = (props: ITextInputProps) => { + ); }; @@ -41,7 +43,7 @@ const InputContainer = styled.div` margin: 16px 0; - font-family: 'Strawford'; // update to fontFamily theme + font-family: 'Strawford'; // update to fontFamily theme `; const LabelContainer = styled.div` @@ -49,6 +51,7 @@ const LabelContainer = styled.div` justify-content: space-between; font-weight: 500; + color: ${props => props.theme.textColorPrimary ?? '#000'} `; const Input = styled.input` @@ -58,14 +61,13 @@ const Input = styled.input` width: 330px; background: #FFFFFF; - border: 1px solid ${(props => props.theme.btnOutline)}; - box-shadow: -1px -1px 2px ${(props => props.theme.btnOutline)}, 1px 1px 2px ${(props => props.theme.btnOutline)}; + border: 2px solid ${(props => props.theme.btnOutline)}; border-radius: 12px; - font-family: 'Strawford'; // update to fontFamily theme + font-family: 'Strawford'; // update to fontFamily theme font-size: 14px; `; const CharCounter = styled.div` color: ${(props => props.theme.textColorSecondary)}; -`; \ No newline at end of file +`; diff --git a/packages/uiweb/src/lib/components/space/theme/index.ts b/packages/uiweb/src/lib/components/space/theme/index.ts index 99c70253e..52c116397 100644 --- a/packages/uiweb/src/lib/components/space/theme/index.ts +++ b/packages/uiweb/src/lib/components/space/theme/index.ts @@ -36,7 +36,7 @@ export const lightTheme: ISpacesTheme = { containerBorderRadius: '12px', statusColorError: '#E93636', statusColorSuccess: '#30CC8B', - iconColorPrimary: '#82828A' + iconColorPrimary: '#82828A', }; export const darkTheme: ISpacesTheme = { @@ -54,5 +54,5 @@ export const darkTheme: ISpacesTheme = { containerBorderRadius: '12px', statusColorError: '#E93636', statusColorSuccess: '#30CC8B', - iconColorPrimary: '#71717A' + iconColorPrimary: '#71717A', }; diff --git a/packages/uiweb/src/lib/context/chatAndNotification/chat/chatMainStateContext.tsx b/packages/uiweb/src/lib/context/chatAndNotification/chat/chatMainStateContext.tsx index 81bf69b92..53dcfe7eb 100644 --- a/packages/uiweb/src/lib/context/chatAndNotification/chat/chatMainStateContext.tsx +++ b/packages/uiweb/src/lib/context/chatAndNotification/chat/chatMainStateContext.tsx @@ -54,20 +54,20 @@ const [finishedFetchingRequests,setFinishedFetchingRequests] = useState setChatsFeed(prevChatsFeed => ({ [id]: newChatFeed , ...prevChatsFeed, - + })); } const setRequestFeed = (id: string,newRequestFeed:IFeeds) => { setRequestsFeed(prevRequestsFeed => ({ [id]: newRequestFeed , ...prevRequestsFeed, - + })); } const setWeb3Name = (id: string,web3Name:string) => { setWeb3NameList(prev => ({ ...prev, - [id]: web3Name + [id]: web3Name })); } @@ -80,7 +80,7 @@ const [finishedFetchingRequests,setFinishedFetchingRequests] = useState return ( - >; @@ -27,6 +35,10 @@ export interface ISpaceDataContextValues { setPgpPrivateKey: React.Dispatch>; env: ENV; setEnv: React.Dispatch>; + chainId: number; + setChainId: React.Dispatch>; + selectedFeedTab: FeedTabs; + setSelectedFeedTab: React.Dispatch>; trendingListData: any; setTrendingListData: React.Dispatch>; spaceInfo: ISpaceInfo; @@ -50,6 +62,9 @@ export interface ISpaceDataContextValues { isListener: boolean; speakerData: ISpaceSpeakerData; setSpeakerData: (key: string, value: PushAPI.video.VideoDataType) => void; + acceptSpaceRequest: (spaceMetaData: PushAPI.video.VideoDataType) => Promise; + connectSpaceRequest: (spaceMetaData: PushAPI.video.VideoDataType) => Promise; + customSearch?: (query: string) => ICustomSearchResult; } export const initialSpaceDataContextValues: ISpaceDataContextValues = { @@ -69,10 +84,18 @@ export const initialSpaceDataContextValues: ISpaceDataContextValues = { setEnv: () => { /**/ }, + chainId: 1, + setChainId: () => { + /** */ + }, trendingListData: null, setTrendingListData: () => { /**/ }, + selectedFeedTab: "Popular" as FeedTabs, + setSelectedFeedTab: () => { + /** */ + }, spaceInfo: {} as ISpaceInfo, setSpaceInfo: () => { /**/ @@ -124,6 +147,13 @@ export const initialSpaceDataContextValues: ISpaceDataContextValues = { setSpeakerData: () => { /** */ }, + acceptSpaceRequest: async () => { + /** */ + }, + connectSpaceRequest: async () => { + /** */ + }, + customSearch: undefined, }; export const SpaceDataContext = createContext( diff --git a/packages/uiweb/src/lib/dataProviders/SpaceDataProvider.tsx b/packages/uiweb/src/lib/dataProviders/SpaceDataProvider.tsx index e716dbaf0..9538a1987 100644 --- a/packages/uiweb/src/lib/dataProviders/SpaceDataProvider.tsx +++ b/packages/uiweb/src/lib/dataProviders/SpaceDataProvider.tsx @@ -14,14 +14,21 @@ import { import { ENV } from '../config'; import * as PushAPI from '@pushprotocol/restapi'; -import { usePushSpaceSocket, useSpaceNotificationSocket } from '../hooks'; +import { useSpaceNotificationSocket } from '../hooks'; import { LivepeerConfig, - Player, createReactClient, studioProvider, } from '@livepeer/react'; +import { spaceChainId } from '../components/space/helpers/account'; +import { walletToPCAIP10 } from '../helpers'; + +export enum FeedTabs { + ForYou = 'For You', + Popular = 'Popular', + HostedByYou = 'Hosted by you', +} export interface ISpacesUIProviderProps { spaceUI: SpacesUI; @@ -35,13 +42,17 @@ export const SpacesUIProvider = ({ children, }: ISpacesUIProviderProps) => { const spacesObjectRef = useRef({} as PushAPI.space.Space); - const [account, setAccount] = useState(spaceUI.account); + const [account, setAccount] = useState(walletToPCAIP10(spaceUI.account)); const [signer, setSigner] = useState(spaceUI.signer); const [pgpPrivateKey, setPgpPrivateKey] = useState( spaceUI.pgpPrivateKey ); const [env, setEnv] = useState(spaceUI.env); + const [chainId, setChainId] = useState( + spaceChainId(spaceUI.account, spaceUI.env) + ); const [spaceWidgetId, setSpaceWidgetId] = useState(''); + const [selectedFeedTab, setSelectedFeedTab] = useState(FeedTabs['Popular']); const [speakerData, setSpeakerData] = useState({} as ISpaceSpeakerData); @@ -76,7 +87,7 @@ export const SpacesUIProvider = ({ const livepeerClient = createReactClient({ provider: studioProvider({ - apiKey: '2638ace1-0a3a-4853-b600-016e6125b9bc', + apiKey: '6d29b32d-78d4-4a5c-9848-a4a0669eb530', }), }); @@ -104,13 +115,49 @@ export const SpacesUIProvider = ({ signer, pgpPrivateKey, address: account, - chainId: 5, // TODO: Make this dynamic + chainId: chainId, env, setSpaceData: setSpaceObjectData, }); await spacesObjectRef.current.initialize({ spaceId }); }; + const acceptSpaceRequest = async ({ + senderAddress, + recipientAddress, + chatId, + signalData, + }: PushAPI.video.VideoDataType) => { + console.log( + 'INSIDE WRAPPER ACCEPT REQUEST', + 'spacesObjectRef?.current', + spacesObjectRef?.current + ); + + await spacesObjectRef.current?.acceptRequest({ + recipientAddress: senderAddress, + senderAddress: recipientAddress, + chatId, + signalData, + }); + }; + + const connectSpaceRequest = async ({ + senderAddress, + signalData, + }: PushAPI.video.VideoDataType) => { + console.log( + 'INSIDE WRAPPER CONNECT', + 'spacesObjectRef?.current', + spacesObjectRef?.current + ); + + await spacesObjectRef.current.connect({ + peerAddress: senderAddress, + signalData, + }); + }; + const getSpaceInfo = (spaceId: string): SpaceDTO | undefined => { return spaceInfo[spaceId]; }; @@ -124,17 +171,22 @@ export const SpacesUIProvider = ({ const existingIds = new Set( prevState.apiData?.map((space: SpaceIFeeds) => space.spaceId) ); - console.log('Existing ID', existingIds); const uniqueSpaces = apiData?.filter( (space) => !existingIds.has(space.spaceId) ); - console.log('Unique Spaces', uniqueSpaces); + + let updatedApiData: SpaceIFeeds[] = []; + if (prevState.apiData) { + updatedApiData = [...prevState.apiData, ...uniqueSpaces]; + updatedApiData.sort((a, b) => new Date(b.intentTimestamp).getTime() - new Date(a.intentTimestamp).getTime()); + } else { + updatedApiData = uniqueSpaces; + } return { ...prevState, - ...(uniqueSpaces && - prevState.apiData && { - apiData: [...prevState.apiData, ...uniqueSpaces], - }), + ...(updatedApiData.length > 0 && { + apiData: updatedApiData, + }), }; } return { @@ -154,17 +206,22 @@ export const SpacesUIProvider = ({ const existingIds = new Set( prevState.apiData?.map((space: SpaceIFeeds) => space.spaceId) ); - console.log('Existing ID', existingIds); const uniqueSpaces = apiData?.filter( (space) => !existingIds.has(space.spaceId) ); - console.log('Unique Spaces', uniqueSpaces); + + let updatedApiData: SpaceIFeeds[] = []; + if (prevState.apiData) { + updatedApiData = [...prevState.apiData, ...uniqueSpaces]; + updatedApiData.sort((a, b) => new Date(b.intentTimestamp).getTime() - new Date(a.intentTimestamp).getTime()); + } else { + updatedApiData = uniqueSpaces; + } return { ...prevState, - ...(uniqueSpaces && - prevState.apiData && { - apiData: [...prevState.apiData, ...uniqueSpaces], - }), + ...(updatedApiData.length > 0 && { + apiData: updatedApiData, + }), }; } return { @@ -184,17 +241,22 @@ export const SpacesUIProvider = ({ const existingIds = new Set( prevState.apiData?.map((space: SpaceIFeeds) => space.spaceId) ); - console.log('Existing ID', existingIds); const uniqueSpaces = apiData?.filter( (space) => !existingIds.has(space.spaceId) ); - console.log('Unique Spaces', uniqueSpaces); + + let updatedApiData: SpaceIFeeds[] = []; + if (prevState.apiData) { + updatedApiData = [...prevState.apiData, ...uniqueSpaces]; + updatedApiData.sort((a, b) => new Date(b.intentTimestamp).getTime() - new Date(a.intentTimestamp).getTime()); + } else { + updatedApiData = uniqueSpaces; + } return { ...prevState, - ...(uniqueSpaces && - prevState.apiData && { - apiData: [...prevState.apiData, ...uniqueSpaces], - }), + ...(updatedApiData.length > 0 && { + apiData: updatedApiData, + }), }; } return { @@ -209,16 +271,16 @@ export const SpacesUIProvider = ({ spaceObjectData?.members?.find((member) => { if ( account?.toUpperCase() === - spaceObjectData.spaceCreator.replace('eip155:', '').toUpperCase() + spaceObjectData.spaceCreator.toUpperCase() ) return false; - const address = member.wallet.replace('eip155:', ''); + const address = member.wallet; return ( address?.toUpperCase() === account?.toUpperCase() && member.isSpeaker ); }) || spaceObjectData?.pendingMembers?.find((member) => { - const address = member.wallet.replace('eip155:', ''); + const address = member.wallet; return ( address?.toUpperCase() === account?.toUpperCase() && member.isSpeaker ); @@ -227,21 +289,22 @@ export const SpacesUIProvider = ({ const isListener = Boolean( spaceObjectData?.members?.find((member) => { - console.log('member', member); - const address = member.wallet.replace('eip155:', ''); + const address = member.wallet; return ( address.toUpperCase() === account.toUpperCase() && !member.isSpeaker ); }) || spaceObjectData?.pendingMembers?.find((member) => { - console.log('pending member', member); - const address = member.wallet.replace('eip155:', ''); + const address = member.wallet; return ( address.toUpperCase() === account.toUpperCase() && !member.isSpeaker ); - }) + }) || + !isSpeaker ); + const customSearch = undefined; + const value: ISpaceDataContextValues = { account, setAccount, @@ -251,6 +314,8 @@ export const SpacesUIProvider = ({ setPgpPrivateKey, env, setEnv, + chainId, + setChainId, trendingListData, setTrendingListData, spaceInfo, @@ -258,6 +323,8 @@ export const SpacesUIProvider = ({ getSpaceInfo, spaceWidgetId, setSpaceWidgetId, + selectedFeedTab, + setSelectedFeedTab, mySpaces, setMySpaces: setMySpacePaginationInfo, popularSpaces, @@ -273,20 +340,49 @@ export const SpacesUIProvider = ({ isListener, speakerData, setSpeakerData: setSpeakerDataItem, + acceptSpaceRequest, + connectSpaceRequest, + customSearch, + }; + + const resetStates = () => { + setSpaceWidgetId(''); + setSpeakerData({} as ISpaceSpeakerData); + setSpaceObjectData(PushAPI.space.initSpaceData); + setSpaceRequests({ + apiData: [] as SpaceIFeeds[], + currentPage: 1, + lastPage: 2, + } as ISpacePaginationData); + setMySpaces({ + apiData: [] as SpaceIFeeds[], + currentPage: 1, + lastPage: 2, + } as ISpacePaginationData); }; useEffect(() => { - setAccount(spaceUI.account); + resetStates(); + setAccount(walletToPCAIP10(spaceUI.account)); setSigner(spaceUI.signer); setEnv(spaceUI.env); setPgpPrivateKey(spaceUI.pgpPrivateKey); - }, [spaceUI]); + + // reset + setChainId(spaceChainId(spaceUI.account, spaceUI.env)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [spaceUI.account, spaceUI.env, spaceUI.pgpPrivateKey, spaceUI.signer]); const PROVIDER_THEME = Object.assign({}, lightTheme, theme); spaceUI.init(); - useSpaceNotificationSocket({ account, env }); - usePushSpaceSocket({ account, env }); + useSpaceNotificationSocket({ + account, + env, + acceptSpaceRequest, + connectSpaceRequest, + }); + // usePushSpaceSocket({ account, env }); return ( diff --git a/packages/uiweb/src/lib/helpers/address.ts b/packages/uiweb/src/lib/helpers/address.ts index 40fbf8d50..5c1912572 100644 --- a/packages/uiweb/src/lib/helpers/address.ts +++ b/packages/uiweb/src/lib/helpers/address.ts @@ -1,13 +1,36 @@ import { ethers } from 'ethers'; import type { Web3Provider } from '@ethersproject/providers'; -export const walletToPCAIP10 = (account: string): string => { - if (account.includes('eip155:')) { - return account; +/** + * + * @param wallet nft:eip155:nftChainId:nftContractAddress:nftTokenId + * @returns + */ +export const isValidCAIP10NFTAddress = (wallet: string): boolean => { + try { + const walletComponent = wallet.split(':'); + return ( + (walletComponent.length === 5 || walletComponent.length === 6)&& + walletComponent[0].toLowerCase() === 'nft' && + !isNaN(Number(walletComponent[4])) && + Number(walletComponent[4]) > 0 && + !isNaN(Number(walletComponent[2])) && + Number(walletComponent[2]) > 0 && + ethers.utils.isAddress(walletComponent[3]) && + walletComponent[1] === 'eip155' + ); + } catch (err) { + return false; } - return 'eip155:' + account; }; +export const walletToPCAIP10 = (account:string): string => { + if(isValidCAIP10NFTAddress(account) || account.includes('eip155:')){ + return account + } + return 'eip155:' + account +} + export const pCAIP10ToWallet = (wallet: string): string => { if(wallet) wallet = wallet.replace('eip155:', ''); diff --git a/packages/uiweb/src/lib/hooks/space/useGetSpaceInfo.ts b/packages/uiweb/src/lib/hooks/space/useGetSpaceInfo.ts index 1e50445c6..f0380cc37 100644 --- a/packages/uiweb/src/lib/hooks/space/useGetSpaceInfo.ts +++ b/packages/uiweb/src/lib/hooks/space/useGetSpaceInfo.ts @@ -8,7 +8,7 @@ import { SpaceDTO } from '@pushprotocol/restapi'; import { ENV } from '../../config'; export const useGetSpaceInfo = (spaceId: string): SpaceDTO | undefined => { - const { getSpaceInfo, setSpaceInfo, env }: ISpaceDataContextValues = + const { getSpaceInfo, setSpaceInfo, env, spaceInfo }: ISpaceDataContextValues = useContext(SpaceDataContext); const [spaceData, setSpaceDataState] = useState( getSpaceInfo(spaceId) @@ -18,6 +18,10 @@ export const useGetSpaceInfo = (spaceId: string): SpaceDTO | undefined => { if (!spaceId) { return; } + if(getSpaceInfo(spaceId)) { + setSpaceDataState(getSpaceInfo(spaceId)); + return; + } const fetchData = async () => { if (!spaceData) { try { @@ -29,9 +33,8 @@ export const useGetSpaceInfo = (spaceId: string): SpaceDTO | undefined => { } } }; - fetchData(); - }, [spaceId, spaceData, getSpaceInfo, setSpaceInfo]); + }, [spaceId, spaceData, getSpaceInfo, setSpaceInfo, spaceInfo, env]); return spaceData; }; diff --git a/packages/uiweb/src/lib/hooks/space/useMySpaces.ts b/packages/uiweb/src/lib/hooks/space/useMySpaces.ts index d78de24ff..67344ff86 100644 --- a/packages/uiweb/src/lib/hooks/space/useMySpaces.ts +++ b/packages/uiweb/src/lib/hooks/space/useMySpaces.ts @@ -17,6 +17,7 @@ export const useMySpaces = (account?: string) => { account: account, page: mySpaces.currentPage, limit: LIMIT, + toDecrypt: false, env, }); diff --git a/packages/uiweb/src/lib/hooks/space/usePushSpaceSocket.tsx b/packages/uiweb/src/lib/hooks/space/usePushSpaceSocket.tsx index 8346b7b3e..979212ba2 100644 --- a/packages/uiweb/src/lib/hooks/space/usePushSpaceSocket.tsx +++ b/packages/uiweb/src/lib/hooks/space/usePushSpaceSocket.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import { ENV } from '../../config'; import { useSpaceData } from './useSpaceData'; import * as PushAPI from "@pushprotocol/restapi"; -import { SpaceDTO, SpaceIFeeds } from '@pushprotocol/restapi'; +import { SpaceDTO } from '@pushprotocol/restapi'; const SPACE_SOCKET_TYPE = 'chat'; @@ -16,7 +16,7 @@ export const usePushSpaceSocket = ({ account, env = ENV.PROD, }: PushSDKSocketHookOptions) => { - const { spaceRequests, setSpaceRequests } = useSpaceData(); + const { spaceRequests, setSpaceRequests, popularSpaces, setPopularSpaces, mySpaces, setMySpaces, setSpaceInfo } = useSpaceData(); const [pushSpaceSocket, setPushSpaceSocket] = useState(null); const [isPushSDKSocketConnected, setIsPushSDKSocketConnected] = useState(false); @@ -37,26 +37,95 @@ export const usePushSpaceSocket = ({ } ); - pushSpaceSocket?.on('SPACES', (spaceInfo: SpaceDTO) => { - console.log(spaceInfo); + pushSpaceSocket?.on('SPACES', async (spaceInfo: SpaceDTO) => { + console.log(spaceInfo, spaceRequests, popularSpaces, mySpaces); - const updatedData = spaceRequests?.apiData?.map(item => { - if (item.spaceId === spaceInfo.spaceId) { - return { - ...item, - spaceInformation: spaceInfo - }; + /* TODO: In future, store all space info in SpaceInfo state itself, and mySpaces, popularSpaces, requests only store spaceId + so as to only update spaceInfo once and it should reflect at every place*/ + setSpaceInfo(spaceInfo.spaceId, spaceInfo); + + const isSpaceInvite: boolean = spaceInfo?.pendingMembers?.some(member => member.wallet === account); + if(isSpaceInvite) { + const isInInvites: boolean = spaceRequests?.apiData?.some(invite => invite.spaceId === spaceInfo.spaceId) ?? false; + if (isInInvites) { + const updatedRequests = spaceRequests?.apiData?.map(item => { + if (item.spaceId === spaceInfo.spaceId) { + return { + ...item, + spaceInformation: spaceInfo + }; + } + return item; + }); + + setSpaceRequests({ + apiData: updatedRequests as PushAPI.SpaceIFeeds[] + }) + } else { + const spaceFeed: PushAPI.SpaceIFeeds = await PushAPI.space.space({ + account: account as string, + env, + recipient: spaceInfo.spaceId, + toDecrypt: false + }) + const updatedRequests: PushAPI.SpaceIFeeds[] = [ + spaceFeed, + ...(spaceRequests?.apiData || []), + ]; + + setSpaceRequests({ + apiData: updatedRequests as PushAPI.SpaceIFeeds[] + }) + } + } else { + const isInMySpaces: boolean = mySpaces?.apiData?.some(invite => invite.spaceId === spaceInfo.spaceId) ?? false; + if (isInMySpaces) { + const updatedMySpaces = mySpaces?.apiData?.map(item => { + if (item.spaceId === spaceInfo.spaceId) { + return { + ...item, + spaceInformation: spaceInfo + }; + } + return item; + }); + + setMySpaces({ + apiData: updatedMySpaces as PushAPI.SpaceIFeeds[] + }) + } else { + const spaceFeed: PushAPI.SpaceIFeeds = await PushAPI.space.space({ + account: account as string, + env, + recipient: spaceInfo.spaceId, + toDecrypt: false + }) + const updatedMySpaces: PushAPI.SpaceIFeeds[] = [ + spaceFeed, + ...(mySpaces?.apiData || []), + ]; + + setMySpaces({ + apiData: updatedMySpaces as PushAPI.SpaceIFeeds[] + }) + } } - return item; - }); - - setSpaceRequests({ - apiData: updatedData as PushAPI.SpaceIFeeds[] - }) + + const updatedPopularSpaces = popularSpaces?.apiData?.map(item => { + if (item.spaceId === spaceInfo.spaceId) { + return { + ...item, + spaceInformation: spaceInfo + }; + } + return item; + }); + + setPopularSpaces({ + apiData: updatedPopularSpaces as PushAPI.SpaceIFeeds[] + }) }); - }, [ - pushSpaceSocket - ]); + }, [account, mySpaces, popularSpaces, pushSpaceSocket, setMySpaces, setPopularSpaces, setSpaceInfo, setSpaceRequests, spaceRequests, env]); const removeSocketEvents = useCallback(() => { pushSpaceSocket?.off(EVENTS.CONNECT); diff --git a/packages/uiweb/src/lib/hooks/space/useSpaceNotificationSocket.ts b/packages/uiweb/src/lib/hooks/space/useSpaceNotificationSocket.ts index a8daa4ac6..a5ef2fdca 100644 --- a/packages/uiweb/src/lib/hooks/space/useSpaceNotificationSocket.ts +++ b/packages/uiweb/src/lib/hooks/space/useSpaceNotificationSocket.ts @@ -1,22 +1,28 @@ import { useCallback, useEffect, useState } from 'react'; import { createSocketConnection, EVENTS } from '@pushprotocol/socket'; import * as PushAPI from '@pushprotocol/restapi'; -import { useSpaceData } from './useSpaceData'; import { ENV } from '../../config'; +import { pCAIP10ToWallet } from '../../helpers'; const NOTIFICATION_SOCKET_TYPE = 'notification'; export type SDKSpaceNotificationSocketHookOptions = { account?: string | null; env?: ENV; + acceptSpaceRequest: ( + spaceMetaData: PushAPI.video.VideoDataType + ) => Promise; + connectSpaceRequest: ( + spaceMetaData: PushAPI.video.VideoDataType + ) => Promise; }; export const useSpaceNotificationSocket = ({ account, + acceptSpaceRequest, + connectSpaceRequest, env = ENV.PROD, }: SDKSpaceNotificationSocketHookOptions) => { - const { spacesObjectRef } = useSpaceData(); - const [notificationSocket, setNotificationSocket] = useState(null); const [isNotificationSocketConnected, setIsNotificationSocketConnected] = useState(false); @@ -33,53 +39,44 @@ export const useSpaceNotificationSocket = ({ notificationSocket?.on(EVENTS.USER_FEEDS, (feedItem: any) => { const { payload } = feedItem; - console.log('RECEIVED USER FEEDS NOTIF', payload); + console.log( + 'USER FEEDS NOTIFICATION RECEIVED', + payload?.data?.additionalMeta?.type, + `${PushAPI.payloads.ADDITIONAL_META_TYPE.PUSH_SPACE}+1` + ); if ( payload?.data?.additionalMeta?.type === - `${PushAPI.payloads.ADDITIONAL_META_TYPE.PUSH_VIDEO}+1` + `${PushAPI.payloads.ADDITIONAL_META_TYPE.PUSH_SPACE}+1` ) { - const { - status, - callDetails, - senderAddress, - recipientAddress, - signalData, - chatId, - }: PushAPI.video.VideoDataType = JSON.parse( + const receivedSpaceMetaData: PushAPI.video.VideoDataType = JSON.parse( payload.data.additionalMeta.data ); + const { callDetails, status } = receivedSpaceMetaData; + + console.log('RECEIVED ADDITIONAL META DATA', receivedSpaceMetaData); + if (status === PushAPI.VideoCallStatus.INITIALIZED) { if ( callDetails?.type === PushAPI.payloads.SPACE_REQUEST_TYPE.JOIN_SPEAKER ) { + console.log( + 'ON HOST, ACCEPTING REQUEST OF AN ADDED SPEAKER TO JOIN' + ); // TODO: see if check for speaker is req - spacesObjectRef.current?.acceptRequest({ - senderAddress: recipientAddress, - recipientAddress: senderAddress, - signalData, - chatId, - }); + acceptSpaceRequest(receivedSpaceMetaData); } if ( callDetails?.type === PushAPI.payloads.SPACE_REQUEST_TYPE.ESTABLISH_MESH ) { - spacesObjectRef.current?.acceptRequest({ - signalData, - senderAddress: recipientAddress, - recipientAddress: senderAddress, - chatId: chatId, - }); + acceptSpaceRequest(receivedSpaceMetaData); } } if (status === PushAPI.VideoCallStatus.RECEIVED) { - spacesObjectRef.current?.connect({ - signalData, - peerAddress: senderAddress, - }); + connectSpaceRequest(receivedSpaceMetaData); } if (status === PushAPI.VideoCallStatus.DISCONNECTED) { if ( @@ -95,7 +92,7 @@ export const useSpaceNotificationSocket = ({ } } }); - }, [notificationSocket]); + }, [acceptSpaceRequest, connectSpaceRequest, notificationSocket]); const removeSocketEvents = useCallback(() => { notificationSocket?.off(EVENTS.CONNECT); @@ -128,7 +125,7 @@ export const useSpaceNotificationSocket = ({ } const main = async () => { const connectionObject = createSocketConnection({ - user: account, + user: pCAIP10ToWallet(account), env, socketType: NOTIFICATION_SOCKET_TYPE, socketOptions: { autoConnect: true, reconnectionAttempts: 3 }, diff --git a/packages/uiweb/src/lib/hooks/space/useSpaceRequests.ts b/packages/uiweb/src/lib/hooks/space/useSpaceRequests.ts index b9c67c0a7..4871b917b 100644 --- a/packages/uiweb/src/lib/hooks/space/useSpaceRequests.ts +++ b/packages/uiweb/src/lib/hooks/space/useSpaceRequests.ts @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import * as PushAPI from '@pushprotocol/restapi'; export const useSpaceRequests = (account?: string) => { - const LIMIT = 2; + const LIMIT = 10; const { spaceRequests, setSpaceRequests, env } = useSpaceData(); const [loading, setLoading] = useState(false); @@ -17,8 +17,8 @@ export const useSpaceRequests = (account?: string) => { account: account, page: spaceRequests.currentPage, limit: LIMIT, + toDecrypt: false, env, - }); const newSpaceRequests = res; diff --git a/packages/uiweb/src/lib/icons/CalendarPurple.tsx b/packages/uiweb/src/lib/icons/CalendarPurple.tsx index a3e4f8524..20c528ee1 100644 --- a/packages/uiweb/src/lib/icons/CalendarPurple.tsx +++ b/packages/uiweb/src/lib/icons/CalendarPurple.tsx @@ -1,6 +1,6 @@ import React from 'react'; -export const CalendarPurple = ({ height, width }: { height?: string, width?: string }) => { +export const CalendarPurple = ({ height, width, color }: { height?: string, width?: string, color?: string }) => { return ( < svg width={width || "15"} @@ -11,14 +11,14 @@ export const CalendarPurple = ({ height, width }: { height?: string, width?: str > { + return ( + + + + + +); +} diff --git a/packages/uiweb/src/lib/icons/SpaceEnded.svg b/packages/uiweb/src/lib/icons/SpaceEnded.svg new file mode 100644 index 000000000..0a2f000c1 --- /dev/null +++ b/packages/uiweb/src/lib/icons/SpaceEnded.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/uiweb/src/lib/icons/SpacesLogo.tsx b/packages/uiweb/src/lib/icons/SpacesLogo.tsx new file mode 100644 index 000000000..ce6aabf70 --- /dev/null +++ b/packages/uiweb/src/lib/icons/SpacesLogo.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +export const SpacesLogo = ({ height, width, color }: { height?: string, width?: string, color?: string }) => { + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/packages/uiweb/src/lib/icons/lensterVector.svg b/packages/uiweb/src/lib/icons/lensterVector.svg new file mode 100644 index 000000000..5aa27269c --- /dev/null +++ b/packages/uiweb/src/lib/icons/lensterVector.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/uiweb/src/lib/icons/scheduled.tsx b/packages/uiweb/src/lib/icons/scheduled.tsx new file mode 100644 index 000000000..120dc233e --- /dev/null +++ b/packages/uiweb/src/lib/icons/scheduled.tsx @@ -0,0 +1,40 @@ +export const Scheduled = ({ color }: { color?: string }) => { + return ( + + + + + + + ); +}; diff --git a/packages/uiweb/yarn.lock b/packages/uiweb/yarn.lock index 0e9621d0f..b5896656b 100644 --- a/packages/uiweb/yarn.lock +++ b/packages/uiweb/yarn.lock @@ -1683,6 +1683,11 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== +moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"