diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx index b392d9a..78f4f01 100644 --- a/src/components/AppBar/AppBar.tsx +++ b/src/components/AppBar/AppBar.tsx @@ -10,24 +10,17 @@ import { import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { - setAuthState, - setMetadataEvent, - userLogOutAction -} from '../../store/actions' +import { setAuthState, setMetadataEvent } from '../../store/actions' import { State } from '../../store/rootReducer' import { Dispatch } from '../../store/store' import Username from '../username' import { Link, useNavigate } from 'react-router-dom' import { MetadataController, NostrController } from '../../controllers' -import { - appPublicRoutes, - appPrivateRoutes, - getProfileRoute -} from '../../routes' +import { appPublicRoutes, getProfileRoute } from '../../routes' import { clearAuthToken, + clearState, saveNsecBunkerDelegatedKey, shorten } from '../../utils' @@ -99,8 +92,7 @@ export const AppBar = () => { // clear authToken saved in local storage clearAuthToken() - - dispatch(userLogOutAction()) + clearState() // update nsecBunker delegated key after logout const nostrController = NostrController.getInstance() @@ -168,16 +160,6 @@ export const AppBar = () => { > Profile - { - navigate(appPrivateRoutes.relays) - }} - sx={{ - justifyContent: 'center' - }} - > - Relays - { if (res.status === 'rejected') { failedPublishes.push({ relay: relays[index], - error: res.reason - ? res.reason.message || fallbackRejectionReason - : fallbackRejectionReason + error: res.reason.message }) } }) @@ -402,359 +374,4 @@ export class NostrController extends EventEmitter { generateDelegatedKey = (): string => { return NDKPrivateKeySigner.generate().privateKey! } - - /** - * Provides relay map. - * @param npub - user's npub - * @returns - promise that resolves into relay map and a timestamp when it has been updated. - */ - getRelayMap = async ( - npub: string - ): Promise<{ map: RelayMap; mapUpdated: number }> => { - const mostPopularRelays = await this.getMostPopularRelays() - - const pool = new SimplePool() - - // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/65.md - const eventFilter: Filter = { - kinds: [kinds.RelayList], - authors: [npub] - } - - const event = await pool - .get(mostPopularRelays, eventFilter) - .catch((err) => { - return Promise.reject(err) - }) - - if (event) { - // Handle founded 10002 event - const relaysMap: RelayMap = {} - - // 'r' stands for 'relay' - const relayTags = event.tags.filter((tag) => tag[0] === 'r') - - relayTags.forEach((tag) => { - const uri = tag[1] - const relayType = tag[2] - - // if 3rd element of relay tag is undefined, relay is WRITE and READ - relaysMap[uri] = { - write: relayType ? relayType === 'write' : true, - read: relayType ? relayType === 'read' : true - } - }) - - this.getRelayInfo(Object.keys(relaysMap)) - - this.connectToRelays(Object.keys(relaysMap)) - - return Promise.resolve({ map: relaysMap, mapUpdated: event.created_at }) - } else { - return Promise.reject('User relays were not found.') - } - } - - /** - * Publishes relay map. - * @param relayMap - relay map. - * @param npub - user's npub. - * @param extraRelaysToPublish - optional relays to publish relay map. - * @returns - promise that resolves into a string representing publishing result. - */ - publishRelayMap = async ( - relayMap: RelayMap, - npub: string, - extraRelaysToPublish?: string[] - ): Promise => { - const timestamp = Math.floor(Date.now() / 1000) - const relayURIs = Object.keys(relayMap) - - // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/65.md - const tags: string[][] = relayURIs.map((relayURI) => - [ - 'r', - relayURI, - relayMap[relayURI].read && relayMap[relayURI].write - ? '' - : relayMap[relayURI].write - ? 'write' - : 'read' - ].filter((value) => value !== '') - ) - - const newRelayMapEvent: UnsignedEvent = { - kind: kinds.RelayList, - tags, - content: '', - pubkey: npub, - created_at: timestamp - } - - const signedEvent = await this.signEvent(newRelayMapEvent) - - let relaysToPublish = relayURIs - - // Add extra relays if provided - if (extraRelaysToPublish) { - relaysToPublish = [...relaysToPublish, ...extraRelaysToPublish] - } - - // If relay map is empty, use most popular relay URIs - if (!relaysToPublish.length) { - relaysToPublish = await this.getMostPopularRelays() - } - - const publishResult = await this.publishEvent(signedEvent, relaysToPublish) - - if (publishResult && publishResult.length) { - return Promise.resolve( - `Relay Map published on: ${publishResult.join('\n')}` - ) - } - - return Promise.reject('Publishing updated relay map was unsuccessful.') - } - - /** - * Provides most popular relays. - * @param numberOfTopRelays - number representing how many most popular relays to provide - * @returns - promise that resolves into an array of most popular relays - */ - getMostPopularRelays = async ( - numberOfTopRelays: number = 30 - ): Promise => { - const mostPopularRelaysState = store.getState().relays?.mostPopular - - // return most popular relays from app state if present - if (mostPopularRelaysState) return mostPopularRelaysState - - // relays in env - const { VITE_MOST_POPULAR_RELAYS } = import.meta.env - const hardcodedPopularRelays = (VITE_MOST_POPULAR_RELAYS || '').split(' ') - const url = `https://stats.nostr.band/stats_api?method=stats` - - const response = await axios.get(url).catch(() => undefined) - - if (!response) { - return hardcodedPopularRelays //return hardcoded relay list - } - - const data = response.data - - if (!data) { - return hardcodedPopularRelays //return hardcoded relay list - } - - const apiTopRelays = data.relay_stats.user_picks.read_relays - .slice(0, numberOfTopRelays) - .map((relay: ReadRelay) => relay.d) - - if (!apiTopRelays.length) { - return Promise.reject(`Couldn't fetch popular relays.`) - } - - if (store.getState().auth?.loggedIn) { - store.dispatch(setMostPopularRelaysAction(apiTopRelays)) - } - - return apiTopRelays - } - - /** - * Sets information about relays into relays.info app state. - * @param relayURIs - relay URIs to get information about - */ - getRelayInfo = async (relayURIs: string[]) => { - // initialize job request - const jobEventTemplate: EventTemplate = { - content: '', - created_at: Math.round(Date.now() / 1000), - kind: 68001, - tags: [ - ['i', `${JSON.stringify(relayURIs)}`], - ['j', 'relay-info'] - ] - } - - // sign job request event - const jobSignedEvent = await this.signEvent(jobEventTemplate) - - const relays = [ - 'wss://relay.damus.io', - 'wss://relay.primal.net', - 'wss://relayable.org' - ] - - // publish job request - await this.publishEvent(jobSignedEvent, relays) - - console.log('jobSignedEvent :>> ', jobSignedEvent) - - const subscribeWithTimeout = ( - subscription: NDKSubscription, - timeoutMs: number - ): Promise => { - return new Promise((resolve, reject) => { - const eventHandler = (event: NDKEvent) => { - subscription.stop() - resolve(event.content) - } - - subscription.on('event', eventHandler) - - // Set up a timeout to stop the subscription after a specified time - const timeout = setTimeout(() => { - subscription.stop() // Stop the subscription - reject(new Error('Subscription timed out')) // Reject the promise with a timeout error - }, timeoutMs) - - // Handle subscription close event - subscription.on('close', () => clearTimeout(timeout)) - }) - } - - const dvmNDK = new NDK({ - explicitRelayUrls: relays - }) - - await dvmNDK.connect(2000) - - // filter for getting DVM job's result - const sub = dvmNDK.subscribe({ - kinds: [68002 as number], - '#e': [jobSignedEvent.id], - '#p': [jobSignedEvent.pubkey] - }) - - // asynchronously get block number from dvm job with 20 seconds timeout - const dvmJobResult = await subscribeWithTimeout(sub, 20000) - - if (!dvmJobResult) { - return Promise.reject(`Relay(s) information wasn't received`) - } - - let relaysInfo: RelayInfoObject - - try { - relaysInfo = JSON.parse(dvmJobResult) - } catch (error) { - return Promise.reject(`Invalid relay(s) information.`) - } - - if ( - relaysInfo && - !compareObjects(store.getState().relays?.info, relaysInfo) - ) { - store.dispatch(setRelayInfoAction(relaysInfo)) - } - } - - /** - * Establishes connection to relays. - * @param relayURIs - an array of relay URIs - * @returns - promise that resolves into an array of connections - */ - connectToRelays = async (relayURIs: string[]) => { - // Copy of relay connection status - const relayConnectionsStatus: RelayConnectionStatus = JSON.parse( - JSON.stringify(store.getState().relays?.connectionStatus || {}) - ) - - const connectedRelayURLs = this.connectedRelays - ? this.connectedRelays.map((relay) => relay.url) - : [] - - // Check if connections already established - if (compareObjects(connectedRelayURLs, relayURIs)) { - return - } - - const connections = relayURIs - .filter((relayURI) => !connectedRelayURLs.includes(relayURI)) - .map((relayURI) => - Relay.connect(relayURI) - .then((relay) => { - // put connection status into relayConnectionsStatus object - relayConnectionsStatus[relayURI] = relay.connected - ? RelayConnectionState.Connected - : RelayConnectionState.NotConnected - - return relay - }) - .catch(() => { - relayConnectionsStatus[relayURI] = RelayConnectionState.NotConnected - }) - ) - - const connected = await Promise.all(connections) - - // put connected relays into connectedRelays private property, so it can be closed later - this.connectedRelays = connected.filter( - (relay) => relay instanceof Relay && relay.connected - ) as Relay[] - - if (Object.keys(relayConnectionsStatus)) { - if ( - !compareObjects( - store.getState().relays?.connectionStatus, - relayConnectionsStatus - ) - ) { - store.dispatch(setRelayConnectionStatusAction(relayConnectionsStatus)) - } - } - - return Promise.resolve(relayConnectionsStatus) - } - - /** - * Disconnects from relays. - * @param relayURIs - array of relay URIs to disconnect from - */ - disconnectFromRelays = async (relayURIs: string[]) => { - const connectedRelayURLs = this.connectedRelays - ? this.connectedRelays.map((relay) => relay.url) - : [] - - relayURIs - .filter((relayURI) => connectedRelayURLs.includes(relayURI)) - .forEach((relayURI) => { - if (this.connectedRelays) { - const relay = this.connectedRelays.find( - (relay) => relay.url === relayURI - ) - - if (relay) { - // close relay connection - relay.close() - - // remove relay from connectedRelays property - this.connectedRelays = this.connectedRelays.filter( - (relay) => relay.url !== relayURI - ) - } - } - }) - - if (store.getState().relays?.connectionStatus) { - const connectionStatus = JSON.parse( - JSON.stringify(store.getState().relays?.connectionStatus) - ) - - relayURIs.forEach((relay) => { - delete connectionStatus[relay] - }) - - if ( - !compareObjects( - store.getState().relays?.connectionStatus, - connectionStatus - ) - ) { - // Update app state - store.dispatch(setRelayConnectionStatusAction(connectionStatus)) - } - } - } } diff --git a/src/hooks/index.ts b/src/hooks/index.ts deleted file mode 100644 index 16c8633..0000000 --- a/src/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './store' diff --git a/src/hooks/store.ts b/src/hooks/store.ts deleted file mode 100644 index f3e9b21..0000000 --- a/src/hooks/store.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useDispatch, useSelector } from 'react-redux' -import type { Dispatch, RootState } from '../store/store' - -// Use instead of plain `useDispatch` and `useSelector` -export const useAppDispatch = useDispatch.withTypes() -export const useAppSelector = useSelector.withTypes() diff --git a/src/main.tsx b/src/main.tsx index 135d197..d586217 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -15,8 +15,7 @@ store.subscribe( saveState({ auth: store.getState().auth, metadata: store.getState().metadata, - userRobotImage: store.getState().userRobotImage, - relays: store.getState().relays + userRobotImage: store.getState().userRobotImage }) }, 1000) ) diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index cd3d606..8c2f702 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -80,7 +80,7 @@ export const Login = () => { setLoadingSpinnerDesc('Authenticating and finding metadata') const redirectPath = - await authController.authAndGetMetadataAndRelaysMap(pubkey) + await authController.authenticateAndFindMetadata(pubkey) navigateAfterLogin(redirectPath) }) @@ -134,7 +134,7 @@ export const Login = () => { setLoadingSpinnerDesc('Authenticating and finding metadata') const redirectPath = await authController - .authAndGetMetadataAndRelaysMap(publickey) + .authenticateAndFindMetadata(publickey) .catch((err) => { toast.error('Error occurred in authentication: ' + err) return null @@ -229,7 +229,7 @@ export const Login = () => { setLoadingSpinnerDesc('Authenticating and finding metadata') const redirectPath = await authController - .authAndGetMetadataAndRelaysMap(pubkey!) + .authenticateAndFindMetadata(pubkey!) .catch((err) => { toast.error('Error occurred in authentication: ' + err) return null @@ -289,7 +289,7 @@ export const Login = () => { setLoadingSpinnerDesc('Authenticating and finding metadata') const redirectPath = await authController - .authAndGetMetadataAndRelaysMap(pubkey!) + .authenticateAndFindMetadata(pubkey!) .catch((err) => { toast.error('Error occurred in authentication: ' + err) return null diff --git a/src/pages/relays/index.tsx b/src/pages/relays/index.tsx deleted file mode 100644 index 010d24d..0000000 --- a/src/pages/relays/index.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { useEffect, useState } from 'react' -import { Box, List, ListItem, TextField } from '@mui/material' -import RouterIcon from '@mui/icons-material/Router' -import styles from './style.module.scss' -import Switch from '@mui/material/Switch' -import ListItemText from '@mui/material/ListItemText' -import Divider from '@mui/material/Divider' -import { NostrController } from '../../controllers' -import { - RelayMap, - RelayInfoObject, - RelayFee, - RelayConnectionState -} from '../../types' -import LogoutIcon from '@mui/icons-material/Logout' -import { useAppSelector, useAppDispatch } from '../../hooks' -import { - compareObjects, - shorten, - hexToNpub, - capitalizeFirstLetter -} from '../../utils' -import { - setRelayMapAction, - setRelayMapUpdatedAction -} from '../../store/actions' -import { toast } from 'react-toastify' -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' -import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp' -import ContentCopyIcon from '@mui/icons-material/ContentCopy' -import ElectricBoltIcon from '@mui/icons-material/ElectricBolt' -import { Tooltip } from '@mui/material' -import InputAdornment from '@mui/material/InputAdornment' -import Button from '@mui/material/Button' - -export const RelaysPage = () => { - const nostrController = NostrController.getInstance() - - const relaysState = useAppSelector((state) => state.relays) - const usersPubkey = useAppSelector((state) => state.auth?.usersPubkey) - - const dispatch = useAppDispatch() - - const [newRelayURI, setNewRelayURI] = useState() - const [newRelayURIerror, setNewRelayURIerror] = useState() - const [relayMap, setRelayMap] = useState( - relaysState?.map - ) - const [relaysInfo, setRelaysInfo] = useState( - relaysState?.info - ) - const [displayRelaysInfo, setDisplayRelaysInfo] = useState([]) - const [relaysConnectionStatus, setRelaysConnectionStatus] = useState( - relaysState?.connectionStatus - ) - - const webSocketPrefix = 'wss://' - - // Update relay connection status - useEffect(() => { - if ( - !compareObjects(relaysConnectionStatus, relaysState?.connectionStatus) - ) { - setRelaysConnectionStatus(relaysState?.connectionStatus) - } - }, [relaysConnectionStatus, relaysState?.connectionStatus]) - - useEffect(() => { - if (!compareObjects(relaysInfo, relaysState?.info)) { - setRelaysInfo(relaysState?.info) - } - }, [relaysInfo, relaysState?.info]) - - useEffect(() => { - let isMounted = false - - const fetchData = async () => { - if (usersPubkey) { - isMounted = true - - // call async func to fetch relay map - const newRelayMap = await nostrController.getRelayMap(usersPubkey) - - // handle fetched relay map - if (isMounted) { - if ( - !relaysState?.mapUpdated || - newRelayMap.mapUpdated > relaysState?.mapUpdated - ) { - if ( - !relaysState?.map || - !compareObjects(relaysState.map, newRelayMap) - ) { - setRelayMap(newRelayMap.map) - - dispatch(setRelayMapAction(newRelayMap.map)) - } else { - // Update relay map updated timestamp - dispatch(setRelayMapUpdatedAction()) - } - } - } - } - } - - // Publishing relay map can take some time. - // This is why data fetch should happen only if relay map was received more than 5 minutes ago. - if ( - usersPubkey && - (!relaysState?.mapUpdated || - Date.now() - relaysState?.mapUpdated > 5 * 60 * 1000) // 5 minutes - ) { - fetchData() - - // Update relay connection status - if (relaysConnectionStatus) { - const notConnectedRelays = Object.keys(relaysConnectionStatus).filter( - (key) => - relaysConnectionStatus[key] === RelayConnectionState.NotConnected - ) - - if (notConnectedRelays.length) { - nostrController.connectToRelays(notConnectedRelays) - } - } - } - - // cleanup func - return () => { - isMounted = false - } - }, [ - dispatch, - usersPubkey, - relaysState?.map, - relaysState?.mapUpdated, - nostrController, - relaysConnectionStatus - ]) - - useEffect(() => { - // Display notification if an empty relay map has been received - if (relayMap && Object.keys(relayMap).length === 0) { - relayRequirementWarning() - } - }, [relayMap]) - - const relayRequirementWarning = () => - toast.warning('At least one write relay is needed for SIGit to work.') - - const handleLeaveRelay = async (relay: string) => { - if (relayMap) { - const relaysInMap = Object.keys(relayMap).length - const writeRelays = Object.keys(relayMap).filter( - (key) => relayMap[key].write - ) - - // Check if at least one write relay is present in relay map - if ( - relaysInMap <= 1 || - (writeRelays.length === 1 && writeRelays.includes(relay)) - ) { - relayRequirementWarning() - } else { - const relayMapCopy = JSON.parse(JSON.stringify(relayMap)) - // Remove relay from relay map - delete relayMapCopy[relay] - - if (usersPubkey) { - // Publish updated relay map. - const relayMapPublishingRes = await nostrController - .publishRelayMap(relayMapCopy, usersPubkey, [relay]) - .catch((err) => handlePublishRelayMapError(err)) - - if (relayMapPublishingRes) { - toast.success(relayMapPublishingRes) - - setRelayMap(relayMapCopy) - - dispatch(setRelayMapAction(relayMapCopy)) - } - } - - nostrController.disconnectFromRelays([relay]) - } - } - } - - const handlePublishRelayMapError = (err: any) => { - const errorPrefix = 'Error while publishing Relay Map' - - if (Array.isArray(err)) { - err.forEach((errorObj: { relay: string; error: string }) => { - toast.error( - `${errorPrefix} to ${errorObj.relay}. Error: ${errorObj.error || 'Unknown'}` - ) - }) - } else { - toast.error(`${errorPrefix}. Error: ${err.message || 'Unknown'}`) - } - } - - const handleRelayWriteChange = async ( - relay: string, - event: React.ChangeEvent - ) => { - if (relayMap && relayMap[relay]) { - if ( - !event.target.checked && - Object.keys(relayMap).filter((relay) => relayMap[relay].write) - .length === 1 - ) { - relayRequirementWarning() - } else { - const relayMapCopy = JSON.parse(JSON.stringify(relayMap)) - relayMapCopy[relay].write = event.target.checked - - if (usersPubkey) { - // Publish updated relay map - const relayMapPublishingRes = await nostrController - .publishRelayMap(relayMapCopy, usersPubkey) - .catch((err) => handlePublishRelayMapError(err)) - - if (relayMapPublishingRes) { - toast.success(relayMapPublishingRes) - - setRelayMap(relayMapCopy) - - dispatch(setRelayMapAction(relayMapCopy)) - } - } - } - } - } - - const handleAddNewRelay = async () => { - const relayURI = `${webSocketPrefix}${newRelayURI?.trim().replace(webSocketPrefix, '')}` - - // Check if new relay URI is a valid string - if ( - relayURI && - !/^wss:\/\/[-a-zA-Z0-9@:%._\\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}/.test( - relayURI - ) - ) { - if (relayURI !== webSocketPrefix) { - setNewRelayURIerror( - 'New relay URI is not valid. Example of valid relay URI: wss://sigit.relay.io' - ) - } - } else if (relayURI && usersPubkey) { - const connectionStatus = await nostrController.connectToRelays([relayURI]) - - if ( - connectionStatus && - connectionStatus[relayURI] && - connectionStatus[relayURI] === RelayConnectionState.Connected - ) { - const relayMapCopy = JSON.parse(JSON.stringify(relayMap)) - - relayMapCopy[relayURI] = { write: true, read: true } - - // Publish updated relay map - const relayMapPublishingRes = await nostrController - .publishRelayMap(relayMapCopy, usersPubkey) - .catch((err) => handlePublishRelayMapError(err)) - - if (relayMapPublishingRes) { - setRelayMap(relayMapCopy) - setNewRelayURI('') - - dispatch(setRelayMapAction(relayMapCopy)) - - nostrController.getRelayInfo([relayURI]) - - toast.success(relayMapPublishingRes) - } - - setNewRelayURIerror(undefined) - } else { - toast.error(`Relay '${relayURI}' wasn't added.`) - - setNewRelayURIerror(`Connection to '${relayURI}' was unsuccessful.`) - } - } - } - - // Handle relay open and close state - const handleRelayInfo = (relay: string) => { - if (relaysInfo) { - const info = relaysInfo[relay] - - if (info) { - let displayRelaysInfoCopy: string[] = JSON.parse( - JSON.stringify(displayRelaysInfo) - ) - - if (displayRelaysInfoCopy.includes(relay)) { - displayRelaysInfoCopy = displayRelaysInfoCopy.filter( - (rel) => rel !== relay - ) - } else { - displayRelaysInfoCopy.push(relay) - } - - setDisplayRelaysInfo(displayRelaysInfoCopy) - } - } - } - - return ( - - - setNewRelayURI(e.target.value)} - helperText={newRelayURIerror} - error={!!newRelayURIerror} - InputProps={{ - startAdornment: ( - - {webSocketPrefix} - - ) - }} - className={styles.relayURItextfield} - /> - - - - - YOUR RELAYS - - {relayMap && ( - - {Object.keys(relayMap).map((relay, i) => ( - - - - - {relaysInfo && - relaysInfo[relay] && - relaysInfo[relay].limitation && - relaysInfo[relay].limitation?.payment_required && ( - - handleRelayInfo(relay)} - /> - - )} - - - - handleLeaveRelay(relay)} - > - - Leave - - - - - handleRelayInfo(relay)} - className={styles.showInfo} - > - Show info{' '} - {displayRelaysInfo.includes(relay) ? ( - - ) : ( - - )} - - ) : ( - '' - ) - } - /> - handleRelayWriteChange(relay, event)} - /> - - {displayRelaysInfo.includes(relay) && ( - <> - - - - {relaysInfo && - relaysInfo[relay] && - Object.keys(relaysInfo[relay]).map((key: string) => { - const infoTitle = capitalizeFirstLetter( - key.replace('_', ' ') - ) - let infoValue = (relaysInfo[relay] as any)[key] - - switch (key) { - case 'pubkey': - infoValue = shorten(hexToNpub(infoValue), 15) - - break - - case 'limitation': - infoValue = ( -
    - {Object.keys(infoValue).map((valueKey) => ( -
  • - - {capitalizeFirstLetter( - valueKey.split('_').join(' ') - )} - : - {' '} - {`${infoValue[valueKey]}`} -
  • - ))} -
- ) - - break - - case 'fees': - infoValue = ( -
    - {Object.keys(infoValue).map((valueKey) => ( -
  • - - {capitalizeFirstLetter( - valueKey.split('_').join(' ') - )} - : - {' '} - {`${infoValue[valueKey].map((fee: RelayFee) => `${fee.amount} ${fee.unit}`)}`} -
  • - ))} -
- ) - break - default: - break - } - - if (Array.isArray(infoValue)) { - infoValue = infoValue.join(', ') - } - - return ( - - - {infoTitle}: - {' '} - {infoValue} - {key === 'pubkey' ? ( - { - navigator.clipboard.writeText( - hexToNpub( - (relaysInfo[relay] as any)[key] - ) - ) - - toast.success('Copied to clipboard', { - autoClose: 1000, - hideProgressBar: true - }) - }} - /> - ) : null} - - ) - })} -
-
- - )} -
-
- ))} -
- )} -
- ) -} diff --git a/src/pages/relays/style.module.scss b/src/pages/relays/style.module.scss deleted file mode 100644 index 6fcb8b7..0000000 --- a/src/pages/relays/style.module.scss +++ /dev/null @@ -1,106 +0,0 @@ -@import '../../colors.scss'; - -.container { - margin-top: 25px; - - .relayURItextfield { - width: 100%; - } - - .relayAddContainer { - display: flex; - flex-direction: row; - gap: 10px; - width: 100%; - } - - .sectionIcon { - font-size: 30px; - } - - .sectionTitle { - margin-top: 35px; - margin-bottom: 10px; - display: flex; - flex-direction: row; - gap: 5px; - font-size: 1.5rem; - line-height: 2rem; - font-weight: 600; - } - - .relaysContainer { - display: flex; - flex-direction: column; - gap: 15px; - } - - .relay { - border: 1px solid rgba(0, 0, 0, 0.12); - border-radius: 4px; - - .relayDivider { - margin-left: 10px; - margin-right: 10px; - } - - .leaveRelayContainer { - display: flex; - flex-direction: row; - gap: 10px; - cursor: pointer; - } - - .showInfo { - cursor: pointer; - } - - .showInfoIcon { - margin-right: 3px; - margin-bottom: auto; - vertical-align: middle; - } - - .relayInfoContainer { - display: flex; - flex-direction: column; - gap: 5px; - text-wrap: wrap; - } - - .relayInfoTitle { - font-weight: 600; - } - - .relayInfoSubTitle { - font-weight: 500; - } - - .copyItem { - margin-left: 10px; - color: #34495e; - vertical-align: bottom; - cursor: pointer; - } - - .connectionStatus { - border-radius: 9999px; - width: 10px; - height: 10px; - margin-right: 5px; - margin-top: 2px; - } - - .connectionStatusConnected { - background-color: $review-feedback-correct; - } - - .connectionStatusNotConnected { - background-color: $review-feedback-incorrect; - } - - .connectionStatusUnknown { - background-color: $input-text-color; - } - } -} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 4753835..a88732f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -6,15 +6,14 @@ import { ProfilePage } from '../pages/profile' import { hexToNpub } from '../utils' import { SignPage } from '../pages/sign' import { VerifyPage } from '../pages/verify' -import { RelaysPage } from '../pages/relays' +import { ProfileSettingsPage } from '../pages/settings/profile' export const appPrivateRoutes = { homePage: '/', create: '/create', sign: '/sign', verify: '/verify', - profileSettings: '/settings/profile/:npub', - relays: '/relays' + profileSettings: '/settings/profile/:npub' } export const appPublicRoutes = { @@ -66,10 +65,6 @@ export const privateRoutes = [ }, { path: appPrivateRoutes.profileSettings, - element: - }, - { - path: appPrivateRoutes.relays, - element: + element: } ] diff --git a/src/store/actionTypes.ts b/src/store/actionTypes.ts index 18b4063..6e0cc66 100644 --- a/src/store/actionTypes.ts +++ b/src/store/actionTypes.ts @@ -1,7 +1,5 @@ export const RESTORE_STATE = 'RESTORE_STATE' -export const USER_LOGOUT = 'USER_LOGOUT' - export const SET_AUTH_STATE = 'SET_AUTH_STATE' export const UPDATE_LOGIN_METHOD = 'UPDATE_LOGIN_METHOD' export const UPDATE_KEYPAIR = 'UPDATE_KEYPAIR' @@ -11,9 +9,3 @@ export const UPDATE_NSECBUNKER_RELAYS = 'UPDATE_NSECBUNKER_RELAYS' export const SET_METADATA_EVENT = 'SET_METADATA_EVENT' export const SET_USER_ROBOT_IMAGE = 'SET_USER_ROBOT_IMAGE' - -export const SET_RELAY_MAP = 'SET_RELAY_MAP' -export const SET_RELAY_INFO = 'SET_RELAY_INFO' -export const SET_RELAY_MAP_UPDATED = 'SET_RELAY_MAP_UPDATED' -export const SET_MOST_POPULAR_RELAYS = 'SET_MOST_POPULAR_RELAYS' -export const SET_RELAY_CONNECTION_STATUS = 'SET_RELAY_CONNECTION_STATUS' diff --git a/src/store/actions.ts b/src/store/actions.ts index a101199..3bf716b 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -3,7 +3,6 @@ import { State } from './rootReducer' export * from './auth/action' export * from './metadata/action' -export * from './relays/action' export const restoreState = (payload: State) => { return { @@ -16,9 +15,3 @@ export interface RestoreState { type: typeof ActionTypes.RESTORE_STATE payload: State } - -export const userLogOutAction = () => { - return { - type: ActionTypes.USER_LOGOUT - } -} diff --git a/src/store/relays/action.ts b/src/store/relays/action.ts deleted file mode 100644 index 6f95840..0000000 --- a/src/store/relays/action.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as ActionTypes from '../actionTypes' -import { - SetRelayMapAction, - SetMostPopularRelaysAction, - SetRelayInfoAction, - SetRelayConnectionStatusAction, - SetRelayMapUpdatedAction -} from './types' -import { RelayMap, RelayInfoObject, RelayConnectionStatus } from '../../types' - -export const setRelayMapAction = (payload: RelayMap): SetRelayMapAction => ({ - type: ActionTypes.SET_RELAY_MAP, - payload -}) - -export const setRelayInfoAction = ( - payload: RelayInfoObject -): SetRelayInfoAction => ({ - type: ActionTypes.SET_RELAY_INFO, - payload -}) - -export const setMostPopularRelaysAction = ( - payload: string[] -): SetMostPopularRelaysAction => ({ - type: ActionTypes.SET_MOST_POPULAR_RELAYS, - payload -}) - -export const setRelayConnectionStatusAction = ( - payload: RelayConnectionStatus -): SetRelayConnectionStatusAction => ({ - type: ActionTypes.SET_RELAY_CONNECTION_STATUS, - payload -}) - -export const setRelayMapUpdatedAction = (): SetRelayMapUpdatedAction => ({ - type: ActionTypes.SET_RELAY_MAP_UPDATED -}) diff --git a/src/store/relays/reducer.ts b/src/store/relays/reducer.ts deleted file mode 100644 index b4b9854..0000000 --- a/src/store/relays/reducer.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as ActionTypes from '../actionTypes' -import { RelaysDispatchTypes, RelaysState } from './types' - -const initialState: RelaysState = { - map: undefined, - mapUpdated: undefined, - mostPopular: undefined, - info: undefined, - connectionStatus: undefined -} - -const reducer = ( - state = initialState, - action: RelaysDispatchTypes -): RelaysState | null => { - switch (action.type) { - case ActionTypes.SET_RELAY_MAP: - return { ...state, map: action.payload, mapUpdated: Date.now() } - - case ActionTypes.SET_RELAY_MAP_UPDATED: - return { ...state, mapUpdated: Date.now() } - - case ActionTypes.SET_RELAY_INFO: - return { - ...state, - info: { ...state.info, ...action.payload } - } - - case ActionTypes.SET_RELAY_CONNECTION_STATUS: - return { - ...state, - connectionStatus: action.payload - } - - case ActionTypes.SET_MOST_POPULAR_RELAYS: - return { ...state, mostPopular: action.payload } - - case ActionTypes.RESTORE_STATE: - return action.payload.relays - - default: - return state - } -} - -export default reducer diff --git a/src/store/relays/types.ts b/src/store/relays/types.ts deleted file mode 100644 index e1c4da8..0000000 --- a/src/store/relays/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as ActionTypes from '../actionTypes' -import { RestoreState } from '../actions' -import { RelayMap, RelayInfoObject, RelayConnectionStatus } from '../../types' - -export type RelaysState = { - map?: RelayMap - mapUpdated?: number - mostPopular?: string[] - info?: RelayInfoObject - connectionStatus?: RelayConnectionStatus -} - -export interface SetRelayMapAction { - type: typeof ActionTypes.SET_RELAY_MAP - payload: RelayMap -} - -export interface SetMostPopularRelaysAction { - type: typeof ActionTypes.SET_MOST_POPULAR_RELAYS - payload: string[] -} - -export interface SetRelayInfoAction { - type: typeof ActionTypes.SET_RELAY_INFO - payload: RelayInfoObject -} - -export interface SetRelayConnectionStatusAction { - type: typeof ActionTypes.SET_RELAY_CONNECTION_STATUS - payload: RelayConnectionStatus -} - -export interface SetRelayMapUpdatedAction { - type: typeof ActionTypes.SET_RELAY_MAP_UPDATED -} - -export type RelaysDispatchTypes = - | SetRelayMapAction - | SetRelayInfoAction - | SetRelayMapUpdatedAction - | SetMostPopularRelaysAction - | SetRelayConnectionStatusAction - | RestoreState diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 517291c..03c9b5c 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -4,31 +4,15 @@ import authReducer from './auth/reducer' import { AuthState } from './auth/types' import metadataReducer from './metadata/reducer' import userRobotImageReducer from './userRobotImage/reducer' -import { RelaysState } from './relays/types' -import relaysReducer from './relays/reducer' -import * as ActionTypes from './actionTypes' export interface State { auth: AuthState metadata?: Event userRobotImage?: string - relays: RelaysState } -export const appReducer = combineReducers({ +export default combineReducers({ auth: authReducer, metadata: metadataReducer, - userRobotImage: userRobotImageReducer, - relays: relaysReducer + userRobotImage: userRobotImageReducer }) - -// FIXME: define types -export default (state: any, action: any) => { - switch (action.type) { - case ActionTypes.USER_LOGOUT: - return appReducer(undefined, action) - - default: - return appReducer(state, action) - } -} diff --git a/src/store/store.ts b/src/store/store.ts index ab20121..20d9b66 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,11 +1,8 @@ import { configureStore } from '@reduxjs/toolkit' import rootReducer from './rootReducer' -const store = configureStore({ - reducer: rootReducer -}) +const store = configureStore({ reducer: rootReducer }) export default store export type Dispatch = typeof store.dispatch -export type RootState = ReturnType diff --git a/src/types/index.ts b/src/types/index.ts index 9397745..ef2283f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,4 +2,3 @@ export * from './core' export * from './nostr' export * from './profile' export * from './zip' -export * from './relay' diff --git a/src/types/nostr.ts b/src/types/nostr.ts index a7d6f0c..67572d8 100644 --- a/src/types/nostr.ts +++ b/src/types/nostr.ts @@ -8,6 +8,11 @@ export interface SignedEvent { sig: string } +export interface RelaySet { + read: string[] + write: string[] +} + export interface NostrJoiningBlock { block: number encodedEventPointer: string diff --git a/src/types/relay.ts b/src/types/relay.ts deleted file mode 100644 index ee426f2..0000000 --- a/src/types/relay.ts +++ /dev/null @@ -1,232 +0,0 @@ -export interface RelaySet { - read: string[] - write: string[] -} - -export type RelayMap = { - [key: string]: { - read: boolean - write: boolean - } -} - -export interface RelayStats { - relays: number - pubKeys: number - users: number - trusted_users: number - events: number - posts: number - zaps: number - zap_amount: number - daily: Daily - daily_totals: DailyTotals - relay_stats: RelayStats -} - -export interface RelayStats { - user_picks: UserPicks - written: Written -} - -export interface Written { - last_week: LastWeek[] -} - -export interface LastWeek { - d: string - p: number - ps: number - e: number - es: number -} - -export interface UserPicks { - read_relays: ReadRelay[] - write_relays: ReadRelay[] -} - -export interface ReadRelay { - d: string - r: number - w: number - rs: number - ws: number -} - -export interface DailyTotals { - datasets: Datasets2 -} - -export interface Datasets2 { - kind_0: Kind0[] - kind_1: Kind0[] - kind_2: Kind0[] - kind_3: Kind0[] - kind_5: Kind0[] - kind_6: Kind0[] - kind_7: Kind0[] - kind_1984: Kind0[] - kind_9735: Kind0[] - kind_1063: Kind0[] - kind_6969: Kind0[] - kind_9802: Kind0[] - kind_30000: Kind0[] - kind_30001: Kind0[] - kind_30008: Kind0[] - kind_30009: Kind0[] - kind_30017: Kind0[] - kind_30018: Kind0[] - kind_30023: Kind0[] - kind_31337: Kind0[] - totals: Kind0[] - new_profiles: Kind0[] - new_pubkeys: Kind0[] - new_contact_lists: Kind0[] - new_ln: Kind0[] - new_users: Kind0[] - total_zap_amount: Kind0[] - zappers: Kind0[] - zapped_pubkeys: Kind0[] - zapped_events: Kind0[] - zap_providers: Kind0[] -} - -export interface Daily { - datasets: Datasets -} - -export interface Datasets { - kind_0: Kind0[] - kind_1: Kind0[] - kind_2: Kind0[] - kind_3: Kind0[] - kind_5: Kind0[] - kind_6: Kind0[] - kind_7: Kind0[] - kind_1984: Kind0[] - kind_9735: Kind0[] - kind_1063: Kind0[] - kind_6969: Kind0[] - kind_9802: Kind0[] - kind_30000: Kind0[] - kind_30001: Kind0[] - kind_30008: Kind0[] - kind_30009: Kind0[] - kind_30017: Kind0[] - kind_30018: Kind0[] - kind_30023: Kind0[] - kind_31337: Kind0[] - totals: Kind0[] - new_profiles: Kind0[] - new_pubkeys: Kind0[] - new_contact_lists: Kind0[] - new_ln: Kind0[] - new_users: Kind0[] - max_zap_amount: Kind0[] - avg_zap_amount: Kind0[] - total_zap_amount: Kind0[] - active_pubkeys: Kind0[] - active_pubkeys_total: Kind0[] - active_pubkeys_week: Kind0[] - active_pubkeys_total_week: Kind0[] - active_relays: Kind0[] - zappers: Kind0[] - zapped_pubkeys: Kind0[] - zapped_events: Kind0[] - zap_providers: Kind0[] - retention: Retention -} - -export interface Retention { - all: All[] - tr: All[] - bio: All[] - all_curves: Allcurve[] - tr_curves: Allcurve[] - bio_curves: Allcurve[] -} - -export interface Allcurve { - day: number - '2023-02': number - '2023-03': number - '2023-04': number - '2023-05': number - '2023-06': number - '2023-07': number -} - -export interface All { - d: string - signups: number - retained: number - retd_posts: number - retd_replies: number - retd_reposts: number - retd_likes: number - retd_liked: number - retd_liked_pubkeys: number - retd_replied: number - retd_replied_pubkeys: number - retd_zaps_received: number - retd_zaps_received_msats: number - retd_zaps_sent: number - retd_zaps_sent_msats: number - retd_following: number - retd_followers: number - lost_posts: number - lost_replies: number - lost_reposts: number - lost_likes: number - lost_liked: number - lost_liked_pubkeys: number - lost_replied: number - lost_replied_pubkeys: number - lost_zaps_received: number - lost_zaps_received_msats: number - lost_zaps_sent: number - lost_zaps_sent_msats: number - lost_following: number - lost_followers: number -} - -export interface Kind0 { - d: string - c: number -} - -export interface RelayFee { - amount: number - unit: string -} - -export interface RelayInfo { - name: string - description: string - pubkey: string - contact: string - supported_nips: number[] - software: string - version: string - limitation?: { [key: string]: number | boolean } - fees?: { [key: string]: RelayFee[] } -} - -export interface RelayInfoObject { - [key: string]: RelayInfo -} - -export interface RelayInfoItem { - uri: string - info: RelayInfo -} - -export enum RelayConnectionState { - Connected = 'Connected', - NotConnected = 'Failed to connect' -} - -export interface RelayConnectionStatus { - [key: string]: RelayConnectionState -} diff --git a/src/utils/index.ts b/src/utils/index.ts index d2f5ec1..156c643 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,4 +5,3 @@ export * from './misc' export * from './nostr' export * from './string' export * from './zip' -export * from './utils' diff --git a/src/utils/string.ts b/src/utils/string.ts index 09a9313..97e86ea 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -85,11 +85,3 @@ export const parseJson = (content: string): Promise => { } }) } - -/** - * Capitalizes the first character in the string - * @param str string to modify - * @returns modified string - */ -export const capitalizeFirstLetter = (str: string) => - str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() diff --git a/src/utils/utils.ts b/src/utils/utils.ts deleted file mode 100644 index a436fae..0000000 --- a/src/utils/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const compareObjects = ( - obj1: object | null | undefined, - obj2: object | null | undefined -): boolean => { - if (Array.isArray(obj1) && Array.isArray(obj2)) { - const obj1Copy = [...obj1].sort() - const obj2Copy = [...obj2].sort() - - return JSON.stringify(obj1Copy) === JSON.stringify(obj2Copy) - } - - return JSON.stringify(obj1) === JSON.stringify(obj2) -}