// @flow import React, { useEffect } from 'react'; import { Route, Redirect, Switch, withRouter } from 'react-router-dom'; import * as PAGES from 'constants/pages'; import { PAGE_TITLE } from 'constants/pageTitles'; import { useIsLargeScreen } from 'effects/use-screensize'; import { lazyImport } from 'util/lazyImport'; import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment'; import { parseURI, isURIValid } from 'util/lbryURI'; import { SITE_TITLE, WELCOME_VERSION } from 'config'; import LoadingBarOneOff from 'component/loadingBarOneOff'; import { GetLinksData } from 'util/buildHomepage'; import * as CS from 'constants/claim_search'; import { buildUnseenCountStr } from 'util/notifications'; import HomePage from 'page/home'; // @if TARGET='app' const BackupPage = lazyImport(() => import('page/backup' /* webpackChunkName: "backup" */)); // @endif // @if TARGET='web' const Code2257Page = lazyImport(() => import('web/page/code2257' /* webpackChunkName: "code2257" */)); const PrivacyPolicyPage = lazyImport(() => import('web/page/privacypolicy' /* webpackChunkName: "privacypolicy" */)); const TOSPage = lazyImport(() => import('web/page/tos' /* webpackChunkName: "tos" */)); const FypPage = lazyImport(() => import('web/page/fyp' /* webpackChunkName: "fyp" */)); const YouTubeTOSPage = lazyImport(() => import('web/page/youtubetos' /* webpackChunkName: "youtubetos" */)); // @endif const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "signIn" */)); const SignInWalletPasswordPage = lazyImport(() => import('page/signInWalletPassword' /* webpackChunkName: "signInWalletPassword" */) ); const SignUpPage = lazyImport(() => import('page/signUp' /* webpackChunkName: "signUp" */)); const SignInVerifyPage = lazyImport(() => import('page/signInVerify' /* webpackChunkName: "signInVerify" */)); const BuyPage = lazyImport(() => import('page/buy' /* webpackChunkName: "buy" */)); const ReceivePage = lazyImport(() => import('page/receive' /* webpackChunkName: "receive" */)); const SendPage = lazyImport(() => import('page/send' /* webpackChunkName: "send" */)); const SwapPage = lazyImport(() => import('page/swap' /* webpackChunkName: "swap" */)); const WalletPage = lazyImport(() => import('page/wallet' /* webpackChunkName: "wallet" */)); const NotificationsPage = lazyImport(() => import('page/notifications' /* webpackChunkName: "notifications" */)); const CollectionPage = lazyImport(() => import('page/collection' /* webpackChunkName: "collection" */)); const ChannelNew = lazyImport(() => import('page/channelNew' /* webpackChunkName: "channelNew" */)); const ChannelsFollowingDiscoverPage = lazyImport(() => import('page/channelsFollowingDiscover' /* webpackChunkName: "channelsFollowingDiscover" */) ); const ChannelsFollowingPage = lazyImport(() => import('page/channelsFollowing' /* webpackChunkName: "channelsFollowing" */) ); const ChannelsFollowingManage = lazyImport(() => import('page/channelsFollowingManage' /* webpackChunkName: "channelsFollowing" */) ); const ChannelsPage = lazyImport(() => import('page/channels' /* webpackChunkName: "channels" */)); const CheckoutPage = lazyImport(() => import('page/checkoutPage' /* webpackChunkName: "checkoutPage" */)); const CreatorDashboard = lazyImport(() => import('page/creatorDashboard' /* webpackChunkName: "creatorDashboard" */)); const DiscoverPage = lazyImport(() => import('page/discover' /* webpackChunkName: "discover" */)); const EmbedWrapperPage = lazyImport(() => import('page/embedWrapper' /* webpackChunkName: "embedWrapper" */)); const PopoutChatPage = lazyImport(() => import('page/popoutChatWrapper' /* webpackChunkName: "popoutChat" */)); const FileListPublished = lazyImport(() => import('page/fileListPublished' /* webpackChunkName: "fileListPublished" */) ); const FourOhFourPage = lazyImport(() => import('page/fourOhFour' /* webpackChunkName: "fourOhFour" */)); const HelpPage = lazyImport(() => import('page/help' /* webpackChunkName: "help" */)); const InvitePage = lazyImport(() => import('page/invite' /* webpackChunkName: "invite" */)); const InvitedPage = lazyImport(() => import('page/invited' /* webpackChunkName: "invited" */)); const LibraryPage = lazyImport(() => import('page/library' /* webpackChunkName: "library" */)); const ListBlockedPage = lazyImport(() => import('page/listBlocked' /* webpackChunkName: "listBlocked" */)); const ListsPage = lazyImport(() => import('page/lists' /* webpackChunkName: "lists" */)); const PlaylistsPage = lazyImport(() => import('page/playlists' /* webpackChunkName: "lists" */)); const LiveStreamSetupPage = lazyImport(() => import('page/livestreamSetup' /* webpackChunkName: "livestreamSetup" */)); const LivestreamCurrentPage = lazyImport(() => import('page/livestreamCurrent' /* webpackChunkName: "livestreamCurrent" */) ); const OdyseeMembershipPage = lazyImport(() => import('page/odyseeMembership' /* webpackChunkName: "odyseeMembership" */) ); const OwnComments = lazyImport(() => import('page/ownComments' /* webpackChunkName: "ownComments" */)); const PasswordResetPage = lazyImport(() => import('page/passwordReset' /* webpackChunkName: "passwordReset" */)); const PasswordSetPage = lazyImport(() => import('page/passwordSet' /* webpackChunkName: "passwordSet" */)); const PublishPage = lazyImport(() => import('page/publish' /* webpackChunkName: "publish" */)); const ReportContentPage = lazyImport(() => import('page/reportContent' /* webpackChunkName: "reportContent" */)); const ReportPage = lazyImport(() => import('page/report' /* webpackChunkName: "report" */)); const RepostNew = lazyImport(() => import('page/repost' /* webpackChunkName: "repost" */)); const RewardsPage = lazyImport(() => import('page/rewards' /* webpackChunkName: "rewards" */)); const RewardsVerifyPage = lazyImport(() => import('page/rewardsVerify' /* webpackChunkName: "rewardsVerify" */)); const SearchPage = lazyImport(() => import('page/search' /* webpackChunkName: "search" */)); const SettingsStripeCard = lazyImport(() => import('page/settingsStripeCard' /* webpackChunkName: "settingsStripeCard" */) ); const SettingsStripeAccount = lazyImport(() => import('page/settingsStripeAccount' /* webpackChunkName: "settingsStripeAccount" */) ); const SettingsCreatorPage = lazyImport(() => import('page/settingsCreator' /* webpackChunkName: "settingsCreator" */)); const SettingsNotificationsPage = lazyImport(() => import('page/settingsNotifications' /* webpackChunkName: "settingsNotifications" */) ); const SettingsPage = lazyImport(() => import('page/settings' /* webpackChunkName: "settings" */)); const ShowPage = lazyImport(() => import('page/show' /* webpackChunkName: "show" */)); const TagsFollowingManagePage = lazyImport(() => import('page/tagsFollowingManage' /* webpackChunkName: "tagsFollowingManage" */) ); const TagsFollowingPage = lazyImport(() => import('page/tagsFollowing' /* webpackChunkName: "tagsFollowing" */)); const TopPage = lazyImport(() => import('page/top' /* webpackChunkName: "top" */)); const UpdatePasswordPage = lazyImport(() => import('page/passwordUpdate' /* webpackChunkName: "passwordUpdate" */)); const Welcome = lazyImport(() => import('page/welcome' /* webpackChunkName: "welcome" */)); const YoutubeSyncPage = lazyImport(() => import('page/youtubeSync' /* webpackChunkName: "youtubeSync" */)); // Tell the browser we are handling scroll restoration if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; } type Props = { currentScroll: number, isAuthenticated: boolean, location: { pathname: string, search: string, hash: string }, history: { action: string, entries: { title: string }[], goBack: () => void, goForward: () => void, index: number, length: number, location: { pathname: string }, push: (string) => void, state: {}, replaceState: ({}, string, string) => void, listen: (any) => () => void, }, uri: string, title: string, welcomeVersion: number, hasNavigated: boolean, setHasNavigated: () => void, setReferrer: (?string) => void, hasUnclaimedRefereeReward: boolean, homepageData: any, wildWestDisabled: boolean, unseenCount: number, }; type PrivateRouteProps = Props & { component: any, isAuthenticated: boolean, }; function PrivateRoute(props: PrivateRouteProps) { const { component: Component, isAuthenticated, ...rest } = props; const urlSearchParams = new URLSearchParams(props.location.search); const redirectUrl = urlSearchParams.get('redirect'); return ( <Route {...rest} render={(props) => isAuthenticated || !IS_WEB ? ( <Component {...props} /> ) : ( <Redirect to={`/$/${PAGES.AUTH}?redirect=${redirectUrl || props.location.pathname}`} /> ) } /> ); } function AppRouter(props: Props) { const { currentScroll, location: { pathname, search, hash }, isAuthenticated, history, uri, title, welcomeVersion, hasNavigated, setHasNavigated, hasUnclaimedRefereeReward, setReferrer, homepageData, wildWestDisabled, unseenCount, } = props; const { entries, listen, action: historyAction } = history; const entryIndex = history.index; const urlParams = new URLSearchParams(search); const resetScroll = urlParams.get('reset_scroll'); const hasLinkedCommentInUrl = urlParams.get(LINKED_COMMENT_QUERY_PARAM); const tagParams = urlParams.get(CS.TAGS_KEY); const isLargeScreen = useIsLargeScreen(); const homeCategoryPages = React.useMemo(() => { const dynamicRoutes = GetLinksData(homepageData, isLargeScreen).filter( (potentialRoute: any) => potentialRoute && potentialRoute.route ); return dynamicRoutes.map((dynamicRouteProps: RowDataItem) => ( <Route key={dynamicRouteProps.route} path={dynamicRouteProps.route} component={(routerProps) => ( <DiscoverPage {...routerProps} dynamicRouteProps={dynamicRouteProps} hideRepostRibbon /> )} /> )); }, [homepageData, isLargeScreen]); // For people arriving at settings page from deeplinks, know whether they can "go back" useEffect(() => { const unlisten = listen((location, action) => { if (action === 'PUSH') { if (!hasNavigated && setHasNavigated) setHasNavigated(); } }); return unlisten; }, [listen, hasNavigated, setHasNavigated]); useEffect(() => { if (!hasNavigated && hasUnclaimedRefereeReward && !isAuthenticated) { const valid = isURIValid(uri); if (valid) { const { path } = parseURI(uri); if (path !== 'undefined') setReferrer(path); } } }, [hasNavigated, uri, hasUnclaimedRefereeReward, setReferrer, isAuthenticated]); useEffect(() => { const getDefaultTitle = (pathname: string) => { const title = pathname.startsWith('/$/') ? PAGE_TITLE[pathname.substring(3)] : ''; return __(title) || (IS_WEB ? SITE_TITLE : 'Odysee'); }; if (uri) { const { channelName, streamName } = parseURI(uri); if (title) { document.title = title; } else if (streamName) { document.title = streamName; } else if (channelName) { document.title = channelName; } else { document.title = getDefaultTitle(pathname); } } else { document.title = getDefaultTitle(pathname); } if (unseenCount > 0) { document.title = `(${buildUnseenCountStr(unseenCount)}) ${document.title}`; } }, [pathname, entries, entryIndex, title, uri, unseenCount]); useEffect(() => { if (!hasLinkedCommentInUrl) { if (hash && historyAction === 'PUSH') { const id = hash.replace('#', ''); const element = document.getElementById(id); if (element) { window.scrollTo(0, element.offsetTop); } } else { window.scrollTo(0, currentScroll); } } }, [currentScroll, pathname, search, hash, resetScroll, hasLinkedCommentInUrl, historyAction]); // react-router doesn't decode pathanmes before doing the route matching check // We have to redirect here because if we redirect on the server, it might get encoded again // in the browser causing a redirect loop const decodedUrl = decodeURIComponent(pathname) + search; if (decodedUrl !== pathname + search) { return <Redirect to={decodedUrl} />; } return ( <React.Suspense fallback={<LoadingBarOneOff />}> <Switch> {/* @if TARGET='app' */} {welcomeVersion < WELCOME_VERSION && <Route path="/*" component={Welcome} />} {/* @endif */} <Redirect from={`/$/${PAGES.DEPRECATED__CHANNELS_FOLLOWING_MANAGE}`} to={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} /> <Redirect from={`/$/${PAGES.DEPRECATED__CHANNELS_FOLLOWING}`} to={`/$/${PAGES.CHANNELS_FOLLOWING}`} /> <Redirect from={`/$/${PAGES.DEPRECATED__TAGS_FOLLOWING}`} to={`/$/${PAGES.TAGS_FOLLOWING}`} /> <Redirect from={`/$/${PAGES.DEPRECATED__TAGS_FOLLOWING_MANAGE}`} to={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} /> <Redirect from={`/$/${PAGES.DEPRECATED__PUBLISH}`} to={`/$/${PAGES.UPLOAD}`} /> <Redirect from={`/$/${PAGES.DEPRECATED__PUBLISHED}`} to={`/$/${PAGES.UPLOADS}`} /> <Route path={`/`} exact component={HomePage} /> {(!wildWestDisabled || tagParams) && <Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />} {!wildWestDisabled && <Route path={`/$/${PAGES.WILD_WEST}`} exact component={DiscoverPage} />} {homeCategoryPages} <Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} /> <Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} /> <Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} /> <Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} /> <Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} /> <Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} /> <Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} /> {/* @if TARGET='app' */} <Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} /> {/* @endif */} {/* @if TARGET='web' */} <Route path={`/$/${PAGES.CODE_2257}`} exact component={Code2257Page} /> <Route path={`/$/${PAGES.PRIVACY_POLICY}`} exact component={PrivacyPolicyPage} /> <Route path={`/$/${PAGES.TOS}`} exact component={TOSPage} /> <Route path={`/$/${PAGES.FYP}`} exact component={FypPage} /> <Route path={`/$/${PAGES.YOUTUBE_TOS}`} exact component={YouTubeTOSPage} /> {/* @endif */} <Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} /> <Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} /> <Route path={`/$/${PAGES.TOP}`} exact component={TopPage} /> <Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} /> <Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} /> <Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} /> <Route path={`/$/${PAGES.REPORT_CONTENT}`} exact component={ReportContentPage} /> <Route {...props} path={`/$/${PAGES.LIST}/:collectionId`} component={CollectionPage} /> <PrivateRoute {...props} exact path={`/$/${PAGES.YOUTUBE_SYNC}`} component={YoutubeSyncPage} /> <PrivateRoute {...props} exact path={`/$/${PAGES.TAGS_FOLLOWING}`} component={TagsFollowingPage} /> <PrivateRoute {...props} exact path={`/$/${PAGES.CHANNELS_FOLLOWING}`} component={isAuthenticated || !IS_WEB ? ChannelsFollowingPage : DiscoverPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} component={SettingsNotificationsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} component={SettingsStripeCard} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_STRIPE_ACCOUNT}`} component={SettingsStripeAccount} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_UPDATE_PWD}`} component={UpdatePasswordPage} /> <PrivateRoute {...props} exact path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} component={ChannelsFollowingDiscoverPage} /> <PrivateRoute {...props} exact path={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} component={ChannelsFollowingManage} /> <PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} /> <PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} /> <PrivateRoute {...props} path={`/$/${PAGES.REPOST_NEW}`} component={RepostNew} /> <PrivateRoute {...props} path={`/$/${PAGES.UPLOADS}`} component={FileListPublished} /> <PrivateRoute {...props} path={`/$/${PAGES.CREATOR_DASHBOARD}`} component={CreatorDashboard} /> <PrivateRoute {...props} path={`/$/${PAGES.UPLOAD}`} component={PublishPage} /> <PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} /> <PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.REWARDS_VERIFY}`} component={RewardsVerifyPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LISTS}`} component={ListsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.PLAYLISTS}`} component={PlaylistsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} /> <PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} /> <PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LIVESTREAM}`} component={LiveStreamSetupPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LIVESTREAM_CURRENT}`} component={LivestreamCurrentPage} /> <PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} /> <PrivateRoute {...props} path={`/$/${PAGES.RECEIVE}`} component={ReceivePage} /> <PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SWAP}`} component={SwapPage} /> <PrivateRoute {...props} path={`/$/${PAGES.NOTIFICATIONS}`} component={NotificationsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.AUTH_WALLET_PASSWORD}`} component={SignInWalletPasswordPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_OWN_COMMENTS}`} component={OwnComments} /> <PrivateRoute {...props} path={`/$/${PAGES.ODYSEE_MEMBERSHIP}`} component={OdyseeMembershipPage} /> <Route path={`/$/${PAGES.POPOUT}/:channelName/:streamName`} component={PopoutChatPage} /> <Route path={`/$/${PAGES.EMBED}/:claimName`} exact component={EmbedWrapperPage} /> <Route path={`/$/${PAGES.EMBED}/:claimName/:claimId`} exact component={EmbedWrapperPage} /> {/* Below need to go at the end to make sure we don't match any of our pages first */} <Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName/:streamName" exact component={ShowPage} /> <Route path="/*" component={FourOhFourPage} /> </Switch> </React.Suspense> ); } export default withRouter(AppRouter);