Skip to content

Commit

Permalink
feat: display feature status (#745)
Browse files Browse the repository at this point in the history
* feat: display feature status

* fix: hook dep

* fix: react hooks dep
  • Loading branch information
Mo authored Nov 22, 2021
1 parent 0193eea commit cf36647
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 98 deletions.
54 changes: 30 additions & 24 deletions app/assets/javascripts/components/ComponentView/IsExpired.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import { FeatureStatus } from '@standardnotes/snjs';
import { FunctionalComponent } from 'preact';

interface IProps {
expiredDate: string;
componentName: string;
featureStatus: FeatureStatus;
reloadStatus: () => void;
manageSubscription: () => void;
}

const statusString = (featureStatus: FeatureStatus, expiredDate: string, componentName: string) => {
switch (featureStatus) {
case FeatureStatus.InCurrentPlanButExpired:
return `Your subscription expired on ${expiredDate}`;
case FeatureStatus.NoUserSubscription:
return `You do not have an active subscription`;
case FeatureStatus.NotInCurrentPlan:
return `Please upgrade your plan to access ${componentName}`;
default:
return `${componentName} is valid and you should not be seeing this message`;
}
};

export const IsExpired: FunctionalComponent<IProps> = ({
expiredDate,
reloadStatus
}) => {
expiredDate,
featureStatus,
reloadStatus,
componentName,
manageSubscription
}) => {
return (
<div className={'sn-component'}>
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
Expand All @@ -19,37 +39,23 @@ export const IsExpired: FunctionalComponent<IProps> = ({
</div>
<div className={'sk-app-bar-item-column'}>
<div>
<a
className={'sk-label sk-base'}
href={'https://dashboard.standardnotes.com'}
rel={'noopener'}
target={'_blank'}
>
Your subscription expired on {expiredDate}
</a>
<strong>
{statusString(featureStatus, expiredDate, componentName)}
</strong>
<div className={'sk-p'}>
Extensions are in a read-only state.
{componentName} is in a read-only state.
</div>
</div>
</div>
</div>
</div>
<div className={'right'}>
<div className={'sk-app-bar-item'} onClick={() => manageSubscription()}>
<button className={'sn-button small success'}>Manage Subscription</button>
</div>
<div className={'sk-app-bar-item'} onClick={() => reloadStatus()}>
<button className={'sn-button small info'}>Reload</button>
</div>
<div className={'sk-app-bar-item'}>
<div className={'sk-app-bar-item-column'}>
<a
className={'sn-button small warning'}
href={'https://standardnotes.com/help/41/my-extensions-appear-as-expired-even-though-my-subscription-is-still-valid'}
rel={'noopener'}
target={'_blank'}
>
Help
</a>
</div>
</div>
</div>
</div>
</div>
Expand Down
32 changes: 19 additions & 13 deletions app/assets/javascripts/components/ComponentView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentAction, LiveItem, SNComponent } from '@node_modules/@standardnotes/snjs';
import { ComponentAction, FeatureStatus, LiveItem, SNComponent, dateToLocalizedString } from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { FunctionalComponent } from 'preact';
import { toDirective } from '@/components/utils';
Expand All @@ -12,6 +12,7 @@ import { IsExpired } from '@/components/ComponentView/IsExpired';
import { IssueOnLoading } from '@/components/ComponentView/IssueOnLoading';
import { AppState } from '@/ui_models/app_state';
import { ComponentArea } from '@node_modules/@standardnotes/features';
import { openSubscriptionDashboard } from '@/hooks/manageSubscription';

interface IProps {
application: WebApplication;
Expand All @@ -36,8 +37,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
appState,
onLoad,
componentUuid,
templateComponent,
manualDealloc = false,
templateComponent
}) => {
const liveComponentRef = useRef<LiveItem<SNComponent> | null>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);
Expand All @@ -46,7 +46,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
const [isLoading, setIsLoading] = useState(false);
const [isReloading, setIsReloading] = useState(false);
const [loadTimeout, setLoadTimeout] = useState<number | undefined>(undefined);
const [isExpired, setIsExpired] = useState(false);
const [featureStatus, setFeatureStatus] = useState<FeatureStatus | undefined>(undefined);
const [isComponentValid, setIsComponentValid] = useState(true);
const [error, setError] = useState<'offline-restricted' | 'url-missing' | undefined>(undefined);
const [isDeprecated, setIsDeprecated] = useState(false);
Expand All @@ -68,6 +68,10 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
});
};

const manageSubscription = useCallback(() => {
openSubscriptionDashboard(application);
}, [application]);

const reloadStatus = useCallback(() => {
if (!component) {
return;
Expand All @@ -82,12 +86,12 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
}
}();

setIsExpired(component.isExpired);
setFeatureStatus(application.getFeatureStatus(component.identifier));

const readonlyState = application.componentManager.getReadonlyStateForComponent(component);

if (!readonlyState.lockReadonly) {
application.componentManager.setReadonlyStateForComponent(component, isExpired);
application.componentManager.setReadonlyStateForComponent(component, featureStatus !== FeatureStatus.Entitled);
}
setIsComponentValid(!offlineRestricted && !hasUrlError);

Expand All @@ -104,7 +108,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
}
setIsDeprecated(component.isDeprecated);
setDeprecationMessage(component.package_info.deprecation_message);
}, [application.componentManager, component, isComponentValid, isExpired]);
}, [application, component, isComponentValid, featureStatus]);

const dismissDeprecationMessage = () => {
setTimeout(() => {
Expand Down Expand Up @@ -214,10 +218,6 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
};
}, [application.componentManager, component, handleIframeLoad, loadComponent, reloadStatus]);

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const expiredDate = isExpired ? component.dateToLocalizedString(component.valid_until) : '';

const getUrl = () => {
const url = component ? application.componentManager.urlForComponent(component) : '';
return url as string;
Expand Down Expand Up @@ -319,8 +319,14 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
reloadIframe={reloadIframe}
/>
)}
{isExpired && (
<IsExpired expiredDate={expiredDate} reloadStatus={reloadStatus} />
{featureStatus !== FeatureStatus.Entitled && (
<IsExpired
expiredDate={dateToLocalizedString(component.valid_until)}
reloadStatus={reloadStatus}
featureStatus={featureStatus!}
componentName={component.name}
manageSubscription={manageSubscription}
/>
)}
{isDeprecated && !isDeprecationMessageDismissed && (
<IsDeprecated
Expand Down
12 changes: 12 additions & 0 deletions app/assets/javascripts/hooks/manageSubscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SNApplication } from '@standardnotes/snjs';

export function openSubscriptionDashboard(application: SNApplication): void {
application.getNewSubscriptionToken().then((token) => {
if (!token) {
return;
}
window.open(
`${window._dashboard_url}?subscription_token=${token}`
);
});
}
2 changes: 1 addition & 1 deletion app/assets/javascripts/preferences/PreferencesMenu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IconType } from '@/components/Icon';
import { action, makeAutoObservable, observable } from 'mobx';
import { ExtensionsLatestVersions } from '@/preferences/panes/extensions-segments';
import { ContentType, SNComponent } from '@node_modules/@standardnotes/snjs';
import { ContentType, SNComponent } from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { FeatureIdentifier } from '@node_modules/@standardnotes/features/dist/Domain/Feature/FeatureIdentifier';
import { ComponentArea } from '@standardnotes/snjs';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { observer } from '@node_modules/mobx-react-lite';
import { HorizontalSeparator } from '@/components/shared/HorizontalSeparator';
import { dateToLocalizedString } from '@/utils';
import { dateToLocalizedString } from '@standardnotes/snjs';
import { useState } from 'preact/hooks';
import { ChangeEmail } from '@/preferences/panes/account/changeEmail';
import { PasswordWizardType } from '@/types';
Expand Down
3 changes: 1 addition & 2 deletions app/assets/javascripts/preferences/panes/account/Sync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import {
Title,
} from '@/preferences/components';
import { Button } from '@/components/Button';
import { SyncQueueStrategy } from '@node_modules/@standardnotes/snjs';
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs';
import { STRING_GENERIC_SYNC_ERROR } from '@/strings';
import { useState } from '@node_modules/preact/hooks';
import { dateToLocalizedString } from '@/utils';
import { observer } from '@node_modules/mobx-react-lite';
import { WebApplication } from '@/ui_models/application';
import { FunctionComponent } from 'preact';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Text } from '@/preferences/components';
import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { convertTimestampToMilliseconds } from '@standardnotes/snjs';
import { openSubscriptionDashboard } from '@/hooks/manageSubscription';

type Props = {
subscriptionState: SubscriptionState;
Expand Down Expand Up @@ -44,14 +45,8 @@ const StatusText = observer(({ subscriptionState }: Props) => {

export const SubscriptionInformation = observer(
({ subscriptionState, application }: Props) => {
const openSubscriptionDashboard = async () => {
const token = await application?.getNewSubscriptionToken();
if (!token) {
return;
}
window.location.assign(
`${window._dashboard_url}?subscription_token=${token}`
);
const manageSubscription = async () => {
openSubscriptionDashboard(application!);
};

return (
Expand All @@ -61,7 +56,7 @@ export const SubscriptionInformation = observer(
className="min-w-20 mt-3 mr-3"
type="normal"
label="Manage subscription"
onClick={openSubscriptionDashboard}
onClick={manageSubscription}
/>
</>
);
Expand Down
25 changes: 0 additions & 25 deletions app/assets/javascripts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,6 @@ export function getPlatform(): Platform {
return platformFromString(getPlatformString());
}

let sharedDateFormatter: Intl.DateTimeFormat;
export function dateToLocalizedString(date: Date) {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
if (!sharedDateFormatter) {
const locale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language;
sharedDateFormatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
return sharedDateFormatter.format(date);
} else {
// IE < 11, Safari <= 9.0.
// In English, this generates the string most similar to
// the toLocaleDateString() result above.
return date.toDateString() + ' ' + date.toLocaleTimeString();
}
}

export function isSameDay(dateA: Date, dateB: Date): boolean {
return (
dateA.getFullYear() === dateB.getFullYear() &&
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-react-hooks": "^4.2.1-beta-149b420f6-20211119",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.4.0",
"lodash": "^4.17.21",
Expand All @@ -70,9 +70,9 @@
"@reach/checkbox": "^0.16.0",
"@reach/dialog": "^0.16.2",
"@reach/listbox": "^0.16.2",
"@standardnotes/features": "1.8.1",
"@standardnotes/features": "1.9.0",
"@standardnotes/sncrypto-web": "1.5.3",
"@standardnotes/snjs": "2.17.9",
"@standardnotes/snjs": "2.18.0",
"mobx": "^6.3.5",
"mobx-react-lite": "^3.2.1",
"preact": "^10.5.15",
Expand Down
41 changes: 21 additions & 20 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2147,6 +2147,13 @@
prop-types "^15.7.2"
tslib "^2.3.0"

"@standardnotes/[email protected]":
version "3.8.3"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.8.3.tgz#6e627c1a1a9ebf91d97f52950d099bf7704382e3"
integrity sha512-wz056b3pv8IIX74lYaqjCUvnw3NSow+ex5pn/VlGxg8r7gq19WsmgyXP2BoE7nqKddO1JMlFok+4gdnutYF0Cw==
dependencies:
"@standardnotes/common" "^1.2.1"

"@standardnotes/auth@^3.8.1":
version "3.8.1"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.8.1.tgz#4197fb2f7e223c6bd13a870a3feac3c73294fb3c"
Expand All @@ -2166,18 +2173,12 @@
dependencies:
"@standardnotes/auth" "^3.8.1"

"@standardnotes/[email protected]":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.8.1.tgz#addea86a8a4288a0046bc674a8e76f1bac3c2f89"
integrity sha512-yDbMLu9SnwR4cmjsIMgpxBOK9kIbWVQ7WezfJ5MN0FAwD2IbfKCMaWBMUJStCtSqkKE4dkL3BC+sIxik/4RwdQ==
dependencies:
"@standardnotes/common" "^1.2.1"

"@standardnotes/features@^1.8.2":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.8.2.tgz#ecc8162ca80ee3740468aa55a64c23b519c13505"
integrity sha512-EYeYYT/WBIh7fMVTVhsy0INjVdyHVtnH90pbZQHNPpsUkLdj05DzuyxUKjsc0oxiskmZjPOVSfXt8l3RL5Av4g==
"@standardnotes/[email protected]", "@standardnotes/features@^1.8.3":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.9.0.tgz#6b563dbc177592a6c0741abc7794b0cd6f1989e0"
integrity sha512-fuRfLrnKEq43ti7FpYZhZu9SLk6j8ruuLcDygoS8gDKB3LU3yuAbKfumqnt0NDF/hXg5/y5FhxxjYQX2GxykoA==
dependencies:
"@standardnotes/auth" "3.8.3"
"@standardnotes/common" "^1.2.1"

"@standardnotes/settings@^1.2.1":
Expand All @@ -2199,15 +2200,15 @@
buffer "^6.0.3"
libsodium-wrappers "^0.7.9"

"@standardnotes/snjs@2.17.9":
version "2.17.9"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.17.9.tgz#9e7402252c6acda6471cc9d887516f17b6bd2cb2"
integrity sha512-Hi57vIByRD5qEOFfQrq/qILH5qRgngyn57qhHE2yKDsX//CTDsdqKGKVZbG0W54rQ+JxYUfjAuBPR/pxIVTi6w==
"@standardnotes/snjs@2.18.0":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.18.0.tgz#0256998cc63902a54bc211aa6808d3dd64d13ef2"
integrity sha512-wwe+zyN7hYUZaoqVSinNNeKO0ng5xqYt0MD6Ga31EM0i1xm10GVP4QOjyzcgiEjEHbWXpTEC8MSw6gU6Glrwbg==
dependencies:
"@standardnotes/auth" "^3.8.1"
"@standardnotes/common" "^1.2.1"
"@standardnotes/domain-events" "^2.5.1"
"@standardnotes/features" "^1.8.2"
"@standardnotes/features" "^1.8.3"
"@standardnotes/settings" "^1.2.1"
"@standardnotes/sncrypto-common" "^1.5.2"

Expand Down Expand Up @@ -4013,10 +4014,10 @@ eslint-config-prettier@^8.3.0:
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a"
integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==

eslint-plugin-react-hooks@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==
eslint-plugin-react-hooks@^4.2.1-beta-149b420f6-20211119:
version "4.2.1-beta-149b420f6-20211119"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.1-beta-149b420f6-20211119.tgz#183ee5420a6db51b09b64694826dbeda17b492f8"
integrity sha512-wTNvhQhv/c3yyXbHFBhvGJDlOuFSA8F+ih/RAwVlZcJjDll+17u55xCI/1I5CWhaMukoYtizQf+qi52veglSvQ==

eslint-plugin-react@^7.26.1:
version "7.26.1"
Expand Down

0 comments on commit cf36647

Please sign in to comment.