diff --git a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx index b9ceb494d..49eb88a7d 100644 --- a/app/assets/javascripts/preferences/panes/AccountPreferences.tsx +++ b/app/assets/javascripts/preferences/panes/AccountPreferences.tsx @@ -1,4 +1,4 @@ -import { Credentials, Sync } from '@/preferences/panes/account'; +import { Sync, SubscriptionWrapper, Credentials } from '@/preferences/panes/account'; import { PreferencesPane } from '@/preferences/components'; import { observer } from 'mobx-react-lite'; import { WebApplication } from '@/ui_models/application'; @@ -11,6 +11,7 @@ export const AccountPreferences = observer(({application}: Props) => { + ); }); diff --git a/app/assets/javascripts/preferences/panes/account/index.ts b/app/assets/javascripts/preferences/panes/account/index.ts index 4910df077..933896877 100644 --- a/app/assets/javascripts/preferences/panes/account/index.ts +++ b/app/assets/javascripts/preferences/panes/account/index.ts @@ -1,2 +1,3 @@ +export { SubscriptionWrapper } from './subscription/SubscriptionWrapper'; export { Sync } from './Sync'; export { Credentials } from './Credentials'; diff --git a/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx b/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx new file mode 100644 index 000000000..5042ea992 --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/subscription/NoSubscription.tsx @@ -0,0 +1,23 @@ +import { FunctionalComponent } from "preact"; +import { Text } from '@/preferences/components'; +import { Button } from '@/components/Button'; + +export const NoSubscription: FunctionalComponent = () => ( + <> + You don't have a Standard Notes subscription yet. +
+
+ +); diff --git a/app/assets/javascripts/preferences/panes/account/subscription/Subscription.tsx b/app/assets/javascripts/preferences/panes/account/subscription/Subscription.tsx new file mode 100644 index 000000000..4f159cb0c --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/subscription/Subscription.tsx @@ -0,0 +1,86 @@ +import { + PreferencesGroup, + PreferencesSegment, + Title, +} from '@/preferences/components'; +import { WebApplication } from '@/ui_models/application'; +import { useCallback, useEffect, useState } from 'preact/hooks'; +import { SubscriptionState } from './subscription_state'; +import { SubscriptionInformation } from './SubscriptionInformation'; +import { NoSubscription } from './NoSubscription'; +import { Text } from '@/preferences/components'; +import { observer } from 'mobx-react-lite'; + +type Props = { + application: WebApplication; + subscriptionState: SubscriptionState; +}; + +export const Subscription = observer(({ + application, + subscriptionState, +}: Props) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + + const { userSubscription } = subscriptionState; + + const getSubscriptions = useCallback(async () => { + try { + const subscriptions = await application.getAvailableSubscriptions(); + if (subscriptions) { + subscriptionState.setAvailableSubscriptions(subscriptions); + } + } catch (e) { + // Error in this call will only prevent the plan name from showing + } + }, [application, subscriptionState]); + + const getSubscription = useCallback(async () => { + try { + const subscription = await application.getUserSubscription(); + if (subscription) { + subscriptionState.setUserSubscription(subscription); + } + } catch (e) { + setError(true); + } + }, [application, subscriptionState]); + + const getSubscriptionInfo = useCallback(async () => { + setLoading(true); + try { + await getSubscription(); + await getSubscriptions(); + } finally { + setLoading(false); + } + }, [getSubscription, getSubscriptions]); + + useEffect(() => { + getSubscriptionInfo(); + }, [getSubscriptionInfo]); + + const now = new Date().getTime(); + + return ( + + +
+
+ Subscription + {error ? ( + No subscription information available. + ) : loading ? ( + Loading subscription information... + ) : userSubscription && userSubscription.endsAt > now ? ( + + ) : ( + + )} +
+
+
+
+ ); +}); diff --git a/app/assets/javascripts/preferences/panes/account/subscription/SubscriptionInformation.tsx b/app/assets/javascripts/preferences/panes/account/subscription/SubscriptionInformation.tsx new file mode 100644 index 000000000..a68b9c53f --- /dev/null +++ b/app/assets/javascripts/preferences/panes/account/subscription/SubscriptionInformation.tsx @@ -0,0 +1,82 @@ +import { observer } from 'mobx-react-lite'; +import { SubscriptionState } from './subscription_state'; +import { Text } from '@/preferences/components'; +import { Button } from '@/components/Button'; + +type Props = { + subscriptionState: SubscriptionState; +}; + +const StatusText = observer(({ subscriptionState }: Props) => { + const { userSubscription, userSubscriptionName } = subscriptionState; + const expirationDate = new Date(userSubscription!.endsAt / 1000).toLocaleString(); + + return userSubscription!.cancelled ? ( + + Your{' '} + + Standard Notes{userSubscriptionName ? ' ' : ''} + {userSubscriptionName} + {' '} + subscription has been{' '} + + canceled but will remain valid until{' '} + {expirationDate} + + . You may resubscribe below if you wish. + + ) : ( + + Your{' '} + + Standard Notes{userSubscriptionName ? ' ' : ''} + {userSubscriptionName} + {' '} + subscription will be{' '} + + renewed on {expirationDate} + + . + + ); +}); + +const PrimaryButton = observer(({ subscriptionState }: Props) => { + const { userSubscription } = subscriptionState; + + return ( +