import { Event, kinds } from 'nostr-tools' import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { Outlet } from 'react-router-dom' import { AppBar } from '../components/AppBar/AppBar' import { LoadingSpinner } from '../components/LoadingSpinner' import { MetadataController, NostrController } from '../controllers' import { restoreState, setAuthState, setMetadataEvent, updateUserAppData } from '../store/actions' import { LoginMethods } from '../store/auth/types' import { State } from '../store/rootReducer' import { Dispatch } from '../store/store' import { setUserRobotImage } from '../store/userRobotImage/action' import { clearAuthToken, clearState, getRoboHashPicture, getUsersAppData, loadState, saveNsecBunkerDelegatedKey, subscribeForSigits } from '../utils' import { useAppSelector } from '../hooks' import { SubCloser } from 'nostr-tools/abstract-pool' import styles from './style.module.scss' import { Footer } from '../components/Footer/Footer' export const MainLayout = () => { const dispatch: Dispatch = useDispatch() const [isLoading, setIsLoading] = useState(true) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`) const authState = useSelector((state: State) => state.auth) const usersAppData = useAppSelector((state) => state.userAppData) useEffect(() => { const metadataController = new MetadataController() const logout = () => { dispatch( setAuthState({ keyPair: undefined, loggedIn: false, usersPubkey: undefined, loginMethod: undefined, nsecBunkerPubkey: undefined }) ) dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent())) // clear authToken saved in local storage clearAuthToken() clearState() // update nsecBunker delegated key const newDelegatedKey = NostrController.getInstance().generateDelegatedKey() saveNsecBunkerDelegatedKey(newDelegatedKey) } const restoredState = loadState() if (restoredState) { dispatch(restoreState(restoredState)) const { loggedIn, loginMethod, usersPubkey, nsecBunkerRelays } = restoredState.auth if (loggedIn) { if (!loginMethod || !usersPubkey) return logout() if (loginMethod === LoginMethods.nsecBunker) { if (!nsecBunkerRelays) return logout() const nostrController = NostrController.getInstance() nostrController.nsecBunkerInit(nsecBunkerRelays).then(() => { nostrController.createNsecBunkerSigner(usersPubkey) }) } const handleMetadataEvent = (event: Event) => { dispatch(setMetadataEvent(event)) } metadataController.on(usersPubkey, (kind: number, event: Event) => { if (kind === kinds.Metadata) { handleMetadataEvent(event) } }) metadataController.findMetadata(usersPubkey).then((metadataEvent) => { if (metadataEvent) handleMetadataEvent(metadataEvent) }) } else { setIsLoading(false) } } else { setIsLoading(false) } }, [dispatch]) useEffect(() => { let subCloser: SubCloser | null = null if (authState.loggedIn && usersAppData) { const pubkey = authState.usersPubkey || authState.keyPair?.public if (pubkey) { subscribeForSigits(pubkey).then((res) => { subCloser = res || null }) } } return () => { if (subCloser) { subCloser.close() } } }, [authState, usersAppData]) /** * When authState change user logged in / or app reloaded * we set robohash avatar in the global state based on user npub * so that avatar will be consistent across the app when kind 0 is empty */ useEffect(() => { if (authState && authState.loggedIn) { const pubkey = authState.usersPubkey || authState.keyPair?.public if (pubkey) { dispatch(setUserRobotImage(getRoboHashPicture(pubkey))) } setIsLoading(true) setLoadingSpinnerDesc(`Fetching user's app data`) getUsersAppData() .then((appData) => { if (appData) { dispatch(updateUserAppData(appData)) } }) .finally(() => setIsLoading(false)) } }, [authState]) if (isLoading) return return ( <>