mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 18:42:58 +03:00
feat(core): payment plans error boundary (#4744)
This commit is contained in:
parent
3c4dbed16b
commit
7a8150398c
@ -382,7 +382,9 @@ export const createConfiguration: (
|
||||
devServer: {
|
||||
hot: 'only',
|
||||
liveReload: true,
|
||||
client: undefined,
|
||||
client: {
|
||||
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
|
||||
},
|
||||
historyApiFallback: true,
|
||||
static: {
|
||||
directory: resolve(rootPath, 'public'),
|
||||
|
@ -45,6 +45,9 @@
|
||||
},
|
||||
{
|
||||
"env": "COVERAGE"
|
||||
},
|
||||
{
|
||||
"env": "DISABLE_DEV_OVERLAY"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
|
@ -2,13 +2,14 @@ import { SubscriptionPlan } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import Tooltip from '@toeverything/components/tooltip';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { withErrorBoundary } from 'react-error-boundary';
|
||||
|
||||
import { openSettingModalAtom } from '../../../atoms';
|
||||
import { useUserSubscription } from '../../../hooks/use-subscription';
|
||||
import * as styles from './style.css';
|
||||
|
||||
export const UserPlanButton = () => {
|
||||
const UserPlanButtonWithData = () => {
|
||||
const [subscription] = useUserSubscription();
|
||||
const plan = subscription?.plan ?? SubscriptionPlan.Free;
|
||||
|
||||
@ -35,3 +36,8 @@ export const UserPlanButton = () => {
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
// If fetch user data failed, just render empty.
|
||||
export const UserPlanButton = withErrorBoundary(UserPlanButtonWithData, {
|
||||
fallbackRender: () => <React.Fragment />,
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
type SubscriptionMutator,
|
||||
useUserSubscription,
|
||||
} from '../../../../../hooks/use-subscription';
|
||||
import { SWRErrorBoundary } from '../../../../pure/swr-error-bundary';
|
||||
import { CancelAction, ResumeAction } from '../plans/actions';
|
||||
import * as styles from './style.css';
|
||||
|
||||
@ -66,20 +67,24 @@ export const BillingSettings = () => {
|
||||
title={t['com.affine.payment.billing-setting.title']()}
|
||||
subtitle={t['com.affine.payment.billing-setting.subtitle']()}
|
||||
/>
|
||||
<Suspense fallback={<SubscriptionSettingSkeleton />}>
|
||||
<SettingWrapper
|
||||
title={t['com.affine.payment.billing-setting.information']()}
|
||||
>
|
||||
<SubscriptionSettings />
|
||||
</SettingWrapper>
|
||||
</Suspense>
|
||||
<Suspense fallback={<BillingHistorySkeleton />}>
|
||||
<SettingWrapper
|
||||
title={t['com.affine.payment.billing-setting.history']()}
|
||||
>
|
||||
<BillingHistory />
|
||||
</SettingWrapper>
|
||||
</Suspense>
|
||||
<SWRErrorBoundary FallbackComponent={SubscriptionSettingSkeleton}>
|
||||
<Suspense fallback={<SubscriptionSettingSkeleton />}>
|
||||
<SettingWrapper
|
||||
title={t['com.affine.payment.billing-setting.information']()}
|
||||
>
|
||||
<SubscriptionSettings />
|
||||
</SettingWrapper>
|
||||
</Suspense>
|
||||
</SWRErrorBoundary>
|
||||
<SWRErrorBoundary FallbackComponent={BillingHistorySkeleton}>
|
||||
<Suspense fallback={<BillingHistorySkeleton />}>
|
||||
<SettingWrapper
|
||||
title={t['com.affine.payment.billing-setting.history']()}
|
||||
>
|
||||
<BillingHistory />
|
||||
</SettingWrapper>
|
||||
</Suspense>
|
||||
</SWRErrorBoundary>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -9,8 +9,10 @@ import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useQuery } from '@affine/workspace/affine/gql';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import React, { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
|
||||
import { SWRErrorBoundary } from '../../../../../components/pure/swr-error-bundary';
|
||||
import { useCurrentLoginStatus } from '../../../../../hooks/affine/use-current-login-status';
|
||||
import { useUserSubscription } from '../../../../../hooks/use-subscription';
|
||||
import { PlanLayout } from './layout';
|
||||
@ -192,9 +194,30 @@ const Settings = () => {
|
||||
|
||||
export const AFFiNECloudPlans = () => {
|
||||
return (
|
||||
// TODO: Error Boundary
|
||||
<Suspense fallback={<PlansSkeleton />}>
|
||||
<Settings />
|
||||
</Suspense>
|
||||
<SWRErrorBoundary FallbackComponent={PlansErrorBoundary}>
|
||||
<Suspense fallback={<PlansSkeleton />}>
|
||||
<Settings />
|
||||
</Suspense>
|
||||
</SWRErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const PlansErrorBoundary = ({ resetErrorBoundary }: FallbackProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const title = t['com.affine.payment.title']();
|
||||
const subtitle = <React.Fragment />;
|
||||
const tabs = <React.Fragment />;
|
||||
const footer = <React.Fragment />;
|
||||
|
||||
const scroll = (
|
||||
<div className={styles.errorTip}>
|
||||
<span>{t['com.affine.payment.plans-error-tip']()}</span>
|
||||
<a onClick={resetErrorBoundary} className={styles.errorTipRetry}>
|
||||
{t['com.affine.payment.plans-error-retry']()}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <PlanLayout {...{ title, subtitle, tabs, scroll, footer }} />;
|
||||
};
|
||||
|
@ -155,3 +155,12 @@ export const downgradeFooter = style({
|
||||
export const textEmphasis = style({
|
||||
color: 'var(--affine-text-emphasis-color)',
|
||||
});
|
||||
|
||||
export const errorTip = style({
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
});
|
||||
export const errorTipRetry = style({
|
||||
textDecoration: 'underline',
|
||||
});
|
||||
|
@ -0,0 +1,56 @@
|
||||
import type { ErrorInfo } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import type { ErrorBoundaryProps } from 'react-error-boundary';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useSWRConfig } from 'swr';
|
||||
|
||||
/**
|
||||
* If we use suspense mode in SWR, we need to preload or delete cache to retry request.
|
||||
* Or the error will be cached and the request will not be retried.
|
||||
*
|
||||
* Reference:
|
||||
* https://github.com/vercel/swr/issues/2740
|
||||
* https://github.com/vercel/swr/blob/main/core/src/use-swr.ts#L690
|
||||
* https://github.com/vercel/swr/tree/main/examples/suspense-retry
|
||||
*/
|
||||
export const SWRErrorBoundary = (props: ErrorBoundaryProps) => {
|
||||
const { onReset, onError } = props;
|
||||
const errorsRef = useRef<Error[]>([]);
|
||||
const { cache } = useSWRConfig();
|
||||
|
||||
const clearErrorCache = React.useCallback(() => {
|
||||
const errors = errorsRef.current;
|
||||
errorsRef.current = [];
|
||||
|
||||
for (const key of cache.keys()) {
|
||||
const item = cache.get(key);
|
||||
if (errors.includes(item?.error)) {
|
||||
cache.delete(key);
|
||||
}
|
||||
}
|
||||
}, [cache]);
|
||||
|
||||
const onResetWithSWR = React.useCallback(
|
||||
(details: any) => {
|
||||
clearErrorCache();
|
||||
onReset?.(details);
|
||||
},
|
||||
[clearErrorCache, onReset]
|
||||
);
|
||||
|
||||
const onErrorWithSWR = React.useCallback(
|
||||
(error: Error, info: ErrorInfo) => {
|
||||
errorsRef.current.push(error);
|
||||
onError?.(error, info);
|
||||
},
|
||||
[onError]
|
||||
);
|
||||
|
||||
React.useEffect(() => clearErrorCache, [clearErrorCache]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary {...props} onReset={onResetWithSWR} onError={onErrorWithSWR}>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
@ -734,6 +734,8 @@
|
||||
"com.affine.payment.updated-notify-title": "Subscription updated",
|
||||
"com.affine.payment.updated-notify-msg": "You have changed your plan to {{plan}} billing.",
|
||||
"com.affine.payment.updated-notify-msg.cancel-subscription": "No further charges will be made starting from the next billing cycle.",
|
||||
"com.affine.payment.plans-error-tip": "Unable to load Pricing plans, please check your network. ",
|
||||
"com.affine.payment.plans-error-retry": "Refresh",
|
||||
"com.affine.storage.maximum-tips": "You have reached the maximum capacity limit for your current account",
|
||||
"com.affine.payment.tag-tooltips": "See all plans",
|
||||
"com.affine.payment.billing-setting.title": "Billing",
|
||||
|
Loading…
Reference in New Issue
Block a user