refactor: use mobx for state and separate files for components
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Sync, Subscription } from '@/preferences/panes/account';
|
||||
import { Sync, SubscriptionWrapper } from '@/preferences/panes/account';
|
||||
import { PreferencesPane } from '@/preferences/components';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
@@ -7,7 +7,7 @@ export const AccountPreferences = observer(({application}: {application: WebAppl
|
||||
return (
|
||||
<PreferencesPane>
|
||||
<Sync application={application} />
|
||||
<Subscription application={application} />
|
||||
<SubscriptionWrapper application={application} />
|
||||
</PreferencesPane>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/preferences/components';
|
||||
import { Button } from '@/components/Button';
|
||||
import { observer } from '@node_modules/mobx-react-lite';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import {
|
||||
GetSubscriptionResponse,
|
||||
GetSubscriptionsResponse,
|
||||
} from '@standardnotes/snjs/dist/@types/services/api/responses';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
type Subscription = {
|
||||
planName: string;
|
||||
cancelled: boolean;
|
||||
endsAt: number;
|
||||
};
|
||||
|
||||
type AvailableSubscriptions = {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
type SubscriptionInformationProps = {
|
||||
subscription?: Subscription;
|
||||
availableSubscriptions: AvailableSubscriptions;
|
||||
};
|
||||
|
||||
type ValidSubscriptionProps = {
|
||||
subscription: Subscription;
|
||||
availableSubscriptions: AvailableSubscriptions;
|
||||
};
|
||||
|
||||
const NoSubscription = () => (
|
||||
<>
|
||||
<Text>You don't have a Standard Notes subscription yet.</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Purchase subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const ActiveSubscription = ({
|
||||
subscription,
|
||||
availableSubscriptions,
|
||||
}: ValidSubscriptionProps) => (
|
||||
<>
|
||||
<Text>
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes {availableSubscriptions[subscription.planName]}
|
||||
</span>{' '}
|
||||
subscription will be{' '}
|
||||
<span className="font-bold">
|
||||
renewed on {new Date(subscription.endsAt).toLocaleString()}
|
||||
</span>
|
||||
.
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Change plan"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Cancel subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const CancelledSubscription = ({
|
||||
subscription,
|
||||
availableSubscriptions,
|
||||
}: ValidSubscriptionProps) => (
|
||||
<>
|
||||
<Text>
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes {availableSubscriptions[subscription.planName]}
|
||||
</span>{' '}
|
||||
subscription has been{' '}
|
||||
<span className="font-bold">
|
||||
canceled but will remain valid until{' '}
|
||||
{new Date(subscription.endsAt).toLocaleString()}
|
||||
</span>
|
||||
. You may resubscribe below if you wish.
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Change plan"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Renew subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const SubscriptionInformation = ({
|
||||
subscription,
|
||||
availableSubscriptions,
|
||||
}: SubscriptionInformationProps) => {
|
||||
const now = new Date().getTime();
|
||||
if (subscription && subscription.endsAt > now) {
|
||||
return subscription.cancelled ? (
|
||||
<CancelledSubscription
|
||||
subscription={subscription}
|
||||
availableSubscriptions={availableSubscriptions}
|
||||
/>
|
||||
) : (
|
||||
<ActiveSubscription
|
||||
subscription={subscription}
|
||||
availableSubscriptions={availableSubscriptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <NoSubscription />;
|
||||
};
|
||||
|
||||
const Subscription = observer(({ application }: Props) => {
|
||||
const [subscription, setSubscription] =
|
||||
useState<Subscription | undefined>(undefined);
|
||||
const [availableSubscriptions, setAvailableSubscriptions] =
|
||||
useState<AvailableSubscriptions>({});
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getSubscriptions = async () => {
|
||||
try {
|
||||
const result = await application.getSubscriptions();
|
||||
if (result.data) {
|
||||
const data = (result as GetSubscriptionsResponse).data;
|
||||
setAvailableSubscriptions(data!);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
const getSubscription = async () => {
|
||||
try {
|
||||
const result = await application.getUserSubscription();
|
||||
if (!result.error && result.data) {
|
||||
const data = (result as GetSubscriptionResponse).data;
|
||||
const subscription = data!.subscription;
|
||||
setSubscription(subscription);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
getSubscriptions();
|
||||
getSubscription();
|
||||
}, [application]);
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex-grow flex flex-col">
|
||||
<Title>Subscription</Title>
|
||||
{error ? (
|
||||
'No subscription information available.'
|
||||
) : (
|
||||
<SubscriptionInformation
|
||||
subscription={subscription}
|
||||
availableSubscriptions={availableSubscriptions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
);
|
||||
});
|
||||
|
||||
export default Subscription;
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as Sync } from './Sync';
|
||||
export { default as Subscription } from './Subscription';
|
||||
export { SubscriptionWrapper } from './subscription/SubscriptionWrapper';
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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;
|
||||
};
|
||||
|
||||
export const ActiveSubscription = observer(
|
||||
({ subscriptionState }: Props) => {
|
||||
const { userSubscription, userSubscriptionName } = subscriptionState;
|
||||
return (
|
||||
<>
|
||||
<Text>
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard Notes{userSubscriptionName ? " " : ""}{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription will be{' '}
|
||||
<span className="font-bold">
|
||||
renewed on {new Date(userSubscription!.endsAt).toLocaleString()}
|
||||
</span>
|
||||
.
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Change plan"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Cancel subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
};
|
||||
|
||||
export const CancelledSubscription = observer(
|
||||
({ subscriptionState }: Props) => {
|
||||
const { userSubscription, userSubscriptionName } = subscriptionState;
|
||||
return (
|
||||
<>
|
||||
<Text>
|
||||
Your{' '}
|
||||
<span className="font-bold">
|
||||
Standard NotesStandard Notes{userSubscriptionName ? " " : ""}{userSubscriptionName}
|
||||
</span>{' '}
|
||||
subscription has been{' '}
|
||||
<span className="font-bold">
|
||||
canceled but will remain valid until{' '}
|
||||
{new Date(userSubscription!.endsAt).toLocaleString()}
|
||||
</span>
|
||||
. You may resubscribe below if you wish.
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Change plan"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Renew subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { FunctionalComponent } from "preact";
|
||||
import { Text } from '@/preferences/components';
|
||||
import { Button } from '@/components/Button';
|
||||
|
||||
export const NoSubscription: FunctionalComponent = () => (
|
||||
<>
|
||||
<Text>You don't have a Standard Notes subscription yet.</Text>
|
||||
<div className="flex">
|
||||
<Button
|
||||
className="min-w-20 mt-3 mr-3"
|
||||
type="normal"
|
||||
label="Refresh"
|
||||
onClick={() => null}
|
||||
/>
|
||||
<Button
|
||||
className="min-w-20 mt-3"
|
||||
type="primary"
|
||||
label="Purchase subscription"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
} from '@/preferences/components';
|
||||
import { observer } from '@node_modules/mobx-react-lite';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import {
|
||||
GetSubscriptionResponse,
|
||||
GetSubscriptionsResponse,
|
||||
} from '@standardnotes/snjs/dist/@types/services/api/responses';
|
||||
import { SubscriptionState } from './subscription_state';
|
||||
import { CancelledSubscription } from './CancelledSubscription';
|
||||
import { ActiveSubscription } from './ActiveSubscription';
|
||||
import { NoSubscription } from './NoSubscription';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
subscriptionState: SubscriptionState;
|
||||
};
|
||||
|
||||
type SubscriptionInformationProps = {
|
||||
subscriptionState: SubscriptionState;
|
||||
};
|
||||
|
||||
const SubscriptionInformation = ({
|
||||
subscriptionState,
|
||||
}: SubscriptionInformationProps) => {
|
||||
const now = new Date().getTime();
|
||||
const { userSubscription } = subscriptionState;
|
||||
|
||||
if (userSubscription && userSubscription.endsAt > now) {
|
||||
return userSubscription.cancelled ? (
|
||||
<CancelledSubscription subscriptionState={subscriptionState} />
|
||||
) : (
|
||||
<ActiveSubscription subscriptionState={subscriptionState} />
|
||||
);
|
||||
}
|
||||
return <NoSubscription />;
|
||||
};
|
||||
|
||||
export const Subscription = observer(({ application, subscriptionState }: Props) => {
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getSubscriptions = async () => {
|
||||
try {
|
||||
const result = await application.getSubscriptions();
|
||||
if (result.data) {
|
||||
const data = (result as GetSubscriptionsResponse).data;
|
||||
subscriptionState.setAvailableSubscriptions(data!);
|
||||
}
|
||||
} catch (e) {
|
||||
// Error in this call will only prevent the plan name from showing
|
||||
}
|
||||
};
|
||||
const getSubscription = async () => {
|
||||
try {
|
||||
const result = await application.getUserSubscription();
|
||||
if (!result.error && result.data) {
|
||||
const data = (result as GetSubscriptionResponse).data;
|
||||
const subscription = data!.subscription;
|
||||
subscriptionState.setUserSubscription(subscription);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
getSubscriptions();
|
||||
getSubscription();
|
||||
}, [application, subscriptionState]);
|
||||
|
||||
return (
|
||||
<PreferencesGroup>
|
||||
<PreferencesSegment>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex-grow flex flex-col">
|
||||
<Title>Subscription</Title>
|
||||
{error ? (
|
||||
'No subscription information available.'
|
||||
) : (
|
||||
<SubscriptionInformation subscriptionState={subscriptionState} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
</PreferencesGroup>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { Subscription } from './Subscription';
|
||||
import { SubscriptionState } from './subscription_state';
|
||||
|
||||
type Props = {
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const SubscriptionWrapper: FunctionalComponent<Props> = ({
|
||||
application,
|
||||
}) => {
|
||||
const subscriptionState = new SubscriptionState();
|
||||
return (
|
||||
<Subscription
|
||||
application={application}
|
||||
subscriptionState={subscriptionState}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
|
||||
type Subscription = {
|
||||
planName: string;
|
||||
cancelled: boolean;
|
||||
endsAt: number;
|
||||
};
|
||||
|
||||
type AvailableSubscriptions = {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export class SubscriptionState {
|
||||
userSubscription: Subscription | undefined;
|
||||
availableSubscriptions: AvailableSubscriptions | undefined;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
userSubscription: observable,
|
||||
availableSubscriptions: observable,
|
||||
|
||||
userSubscriptionName: computed,
|
||||
|
||||
setUserSubscription: action,
|
||||
setAvailableSubscriptions: action,
|
||||
});
|
||||
}
|
||||
|
||||
get userSubscriptionName(): string {
|
||||
if (
|
||||
this.availableSubscriptions &&
|
||||
this.userSubscription &&
|
||||
this.availableSubscriptions[this.userSubscription.planName]
|
||||
) {
|
||||
return this.availableSubscriptions[this.userSubscription.planName].name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public setUserSubscription(subscription: Subscription): void {
|
||||
this.userSubscription = subscription;
|
||||
}
|
||||
|
||||
public setAvailableSubscriptions(
|
||||
subscriptions: AvailableSubscriptions
|
||||
): void {
|
||||
this.availableSubscriptions = subscriptions;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user