Add announcement modal and open it after prefs sync'd.
- Don't want to show it in Incognito. - Only show it in when entered from homepage, or in the Help page. - Record the hash of the viewed announcement and update the wallet with it.
This commit is contained in:
parent
79eb28cc55
commit
3719a73c81
9 changed files with 168 additions and 18 deletions
|
@ -1,6 +1,11 @@
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectGetSyncErrorMessage, selectSyncFatalError, selectSyncIsLocked } from 'redux/selectors/sync';
|
||||
import {
|
||||
selectGetSyncErrorMessage,
|
||||
selectPrefsReady,
|
||||
selectSyncFatalError,
|
||||
selectSyncIsLocked,
|
||||
} from 'redux/selectors/sync';
|
||||
import { doUserSetReferrer } from 'redux/actions/user';
|
||||
import {
|
||||
selectOdyseeMembershipIsPremiumPlus,
|
||||
|
@ -15,7 +20,7 @@ import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
|||
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||
import { selectModal, selectActiveChannelClaim, selectIsReloadRequired } from 'redux/selectors/app';
|
||||
import { selectUploadCount } from 'redux/selectors/publish';
|
||||
import { doSetLanguage } from 'redux/actions/settings';
|
||||
import { doOpenAnnouncements, doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
import { doSignIn, doSetIncognito } from 'redux/actions/app';
|
||||
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
|
||||
|
@ -28,6 +33,7 @@ const select = (state) => ({
|
|||
language: selectLanguage(state),
|
||||
languages: selectLoadedLanguages(state),
|
||||
isReloadRequired: selectIsReloadRequired(state),
|
||||
prefsReady: selectPrefsReady(state),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
syncIsLocked: selectSyncIsLocked(state),
|
||||
uploadCount: selectUploadCount(state),
|
||||
|
@ -51,6 +57,7 @@ const perform = {
|
|||
setIncognito: doSetIncognito,
|
||||
fetchModBlockedList: doFetchModBlockedList,
|
||||
fetchModAmIList: doFetchCommentModAmIList,
|
||||
doOpenAnnouncements,
|
||||
};
|
||||
|
||||
export default hot(connect(select, perform)(App));
|
||||
|
|
|
@ -74,6 +74,7 @@ type Props = {
|
|||
balance: ?number,
|
||||
syncIsLocked: boolean,
|
||||
syncError: ?string,
|
||||
prefsReady: boolean,
|
||||
rewards: Array<Reward>,
|
||||
setReferrer: (string, boolean) => void,
|
||||
isAuthenticated: boolean,
|
||||
|
@ -87,6 +88,7 @@ type Props = {
|
|||
fetchModBlockedList: () => void,
|
||||
fetchModAmIList: () => void,
|
||||
homepageFetched: boolean,
|
||||
doOpenAnnouncements: () => void,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
|
@ -103,6 +105,7 @@ function App(props: Props) {
|
|||
history,
|
||||
syncError,
|
||||
syncIsLocked,
|
||||
prefsReady,
|
||||
language,
|
||||
languages,
|
||||
setLanguage,
|
||||
|
@ -119,6 +122,7 @@ function App(props: Props) {
|
|||
hasPremiumPlus,
|
||||
fetchModAmIList,
|
||||
homepageFetched,
|
||||
doOpenAnnouncements,
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
@ -438,7 +442,7 @@ function App(props: Props) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps, (one time after locale is fetched)
|
||||
}, [locale]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (locale) {
|
||||
const countryCode = locale.country;
|
||||
const langs = getLanguagesForCountry(countryCode) || [];
|
||||
|
@ -471,6 +475,12 @@ function App(props: Props) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [syncError, pathname, isAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prefsReady) {
|
||||
doOpenAnnouncements();
|
||||
}
|
||||
}, [prefsReady]);
|
||||
|
||||
// Keep this at the end to ensure initial setup effects are run first
|
||||
useEffect(() => {
|
||||
if (!hasSignedIn && hasVerifiedEmail) {
|
||||
|
|
|
@ -51,3 +51,4 @@ export const CONFIRM_REMOVE_COMMENT = 'CONFIRM_REMOVE_COMMENT';
|
|||
export const CONFIRM_ODYSEE_MEMBERSHIP = 'CONFIRM_ODYSEE_MEMBERSHIP';
|
||||
export const MEMBERSHIP_SPLASH = 'MEMBERSHIP_SPLASH';
|
||||
export const HIDE_RECOMMENDATION = 'HIDE_RECOMMENDATION';
|
||||
export const ANNOUNCEMENTS = 'ANNOUNCEMENTS';
|
||||
|
|
20
ui/modal/modalAnnouncements/index.js
Normal file
20
ui/modal/modalAnnouncements/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import ModalAnnouncements from './view';
|
||||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||
import { selectLastViewedAnnouncement } from 'redux/selectors/content';
|
||||
import { selectHomepageAnnouncement } from 'redux/selectors/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
const select = (state) => ({
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
announcement: selectHomepageAnnouncement(state),
|
||||
lastViewedHash: selectLastViewedAnnouncement(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
doHideModal,
|
||||
doSetLastViewedAnnouncement,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(ModalAnnouncements);
|
68
ui/modal/modalAnnouncements/view.jsx
Normal file
68
ui/modal/modalAnnouncements/view.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Card from 'component/common/card';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import { Modal } from 'modal/modal';
|
||||
import { getSimpleStrHash } from 'util/string';
|
||||
|
||||
type Props = {
|
||||
isAutoInvoked?: boolean,
|
||||
// --- redux ---
|
||||
authenticated?: boolean,
|
||||
announcement: string,
|
||||
lastViewedHash: ?string,
|
||||
doHideModal: () => void,
|
||||
doSetLastViewedAnnouncement: (hash: string) => void,
|
||||
};
|
||||
|
||||
export default function ModalAnnouncements(props: Props) {
|
||||
const {
|
||||
authenticated,
|
||||
announcement,
|
||||
lastViewedHash,
|
||||
isAutoInvoked,
|
||||
doHideModal,
|
||||
doSetLastViewedAnnouncement,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
location: { pathname },
|
||||
} = useHistory();
|
||||
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!authenticated || (pathname !== '/' && pathname !== `/$/${PAGES.HELP}`) || announcement === '') {
|
||||
doHideModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = getSimpleStrHash(announcement);
|
||||
|
||||
if (lastViewedHash === hash) {
|
||||
if (isAutoInvoked) {
|
||||
doHideModal();
|
||||
} else {
|
||||
setShow(true);
|
||||
}
|
||||
} else {
|
||||
setShow(true);
|
||||
doSetLastViewedAnnouncement(hash);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal type="card" isOpen onAborted={doHideModal}>
|
||||
<Card
|
||||
className="announcement"
|
||||
actions={<MarkdownPreview className="markdown-preview--announcement" content={announcement} simpleLinks />}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -7,6 +7,7 @@ import { lazyImport } from 'util/lazyImport';
|
|||
|
||||
// prettier-ignore
|
||||
const MAP = Object.freeze({
|
||||
[MODALS.ANNOUNCEMENTS]: lazyImport(() => import('modal/modalAnnouncements' /* webpackChunkName: "modalAnnouncements" */)),
|
||||
[MODALS.AFFIRM_PURCHASE]: lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)),
|
||||
[MODALS.AUTO_GENERATE_THUMBNAIL]: lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */)),
|
||||
[MODALS.AUTO_UPDATE_DOWNLOADED]: lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */)),
|
||||
|
|
|
@ -3,6 +3,7 @@ import { doWalletReconnect } from 'redux/actions/wallet';
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import analytics from 'analytics';
|
||||
|
@ -10,7 +11,7 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
|||
import { launcher } from 'util/autoLaunch';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSyncLoop, doSyncUnsubscribe, doSetSyncLock } from 'redux/actions/sync';
|
||||
import { doAlertWaitingForSync, doGetAndPopulatePreferences } from 'redux/actions/app';
|
||||
import { doAlertWaitingForSync, doGetAndPopulatePreferences, doOpenModal } from 'redux/actions/app';
|
||||
import { selectPrefsReady } from 'redux/selectors/sync';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { getDefaultLanguage } from 'util/default-languages';
|
||||
|
@ -317,23 +318,38 @@ function populateCategoryTitles(categories) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadBuiltInHomepageData(dispatch) {
|
||||
const homepages = require('homepages');
|
||||
if (homepages) {
|
||||
const v2 = {};
|
||||
const homepageKeys = Object.keys(homepages);
|
||||
homepageKeys.forEach((hp) => {
|
||||
v2[hp] = homepages[hp];
|
||||
});
|
||||
|
||||
window.homepages = v2;
|
||||
populateCategoryTitles(window.homepages?.en?.categories);
|
||||
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
|
||||
}
|
||||
}
|
||||
|
||||
export function doOpenAnnouncements() {
|
||||
return (dispatch) => {
|
||||
// There is a weird useEffect in modalRouter that closes all modals on
|
||||
// initial mount. Not sure what scenario that covers, so just delay a bit
|
||||
// until it is mounted.
|
||||
setTimeout(() => {
|
||||
dispatch(doOpenModal(MODALS.ANNOUNCEMENTS, { isAutoInvoked: true }));
|
||||
}, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchHomepages() {
|
||||
return (dispatch) => {
|
||||
// -- Use this env flag to use local homepage data. Otherwise, it will grab from `/$/api/content/v*/get`.
|
||||
// -- Use this env flag to use local homepage data and meme (faster).
|
||||
// -- Otherwise, it will grab from `/$/api/content/v*/get`.
|
||||
// @if USE_LOCAL_HOMEPAGE_DATA='true'
|
||||
const homepages = require('homepages');
|
||||
if (homepages) {
|
||||
const v2 = {};
|
||||
const homepageKeys = Object.keys(homepages);
|
||||
homepageKeys.forEach((hp) => {
|
||||
v2[hp] = homepages[hp];
|
||||
});
|
||||
|
||||
window.homepages = v2;
|
||||
populateCategoryTitles(window.homepages?.en?.categories);
|
||||
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
|
||||
return;
|
||||
}
|
||||
loadBuiltInHomepageData(dispatch);
|
||||
// @endif
|
||||
|
||||
fetch('https://odysee.com/$/api/content/v2/get')
|
||||
|
|
|
@ -303,6 +303,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Move .announcement to `modalAnnouncements/style.scss` when structure is ready.
|
||||
.announcement {
|
||||
@media (min-width: $breakpoint-small) {
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-preview--announcement {
|
||||
@media (min-width: $breakpoint-small) {
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
img {
|
||||
max-width: 40%;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-preview {
|
||||
background-color: rgba(var(--color-header-background-base), 1);
|
||||
}
|
||||
|
|
|
@ -26,3 +26,7 @@ export function toCompactNotation(number: string | number, lang: ?string, minThr
|
|||
export function stripLeadingAtSign(str: ?string) {
|
||||
return str && str.charAt(0) === '@' ? str.slice(1) : str;
|
||||
}
|
||||
|
||||
export function getSimpleStrHash(s: string) {
|
||||
return String(s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue