feat(core): adapt storage progress to payment system (#4713)

This commit is contained in:
JimmFly 2023-10-25 16:18:30 +08:00 committed by GitHub
parent eaa90c9fb6
commit df69c908fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 72 deletions

View File

@ -40,6 +40,7 @@
"@toeverything/infra": "workspace:*",
"@toeverything/theme": "^0.7.20",
"@vanilla-extract/dynamic": "^2.0.3",
"bytes": "^3.1.2",
"check-password-strength": "^2.0.7",
"clsx": "^2.0.0",
"dayjs": "^1.11.10",
@ -71,6 +72,7 @@
"@storybook/jest": "^0.2.3",
"@storybook/testing-library": "^0.2.2",
"@testing-library/react": "^14.0.0",
"@types/bytes": "^3.1.3",
"@types/react": "^18.2.28",
"@types/react-datepicker": "^4.19.0",
"@types/react-dnd": "^3.0.2",

View File

@ -115,31 +115,19 @@ globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
height: '8px',
borderRadius: '4px',
backgroundColor: 'var(--affine-pure-black-10)',
backgroundColor: 'var(--affine-black-10)',
overflow: 'hidden',
});
export const storageProgressBar = style({
height: '100%',
backgroundColor: 'var(--affine-processing-color)',
selectors: {
'&.warning': {
// Wait for design
backgroundColor: '#FF7C09',
},
'&.danger': {
backgroundColor: 'var(--affine-error-color)',
},
},
});
export const storageExtendHint = style({
borderRadius: '4px',
padding: '4px 8px',
backgroundColor: 'var(--affine-background-secondary-color)',
color: 'var(--affine-text-secondary-color)',
fontSize: 'var(--affine-font-xs)',
lineHeight: '20px',
marginTop: 8,
});
globalStyle(`${storageExtendHint} a`, {
color: 'var(--affine-link-color)',
export const storageButton = style({
padding: '4px 12px',
});

View File

@ -1,6 +1,7 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button';
import { Tooltip } from '@toeverything/components/tooltip';
import bytes from 'bytes';
import clsx from 'clsx';
import { useMemo } from 'react';
@ -10,20 +11,14 @@ export interface StorageProgressProgress {
max: number;
value: number;
onUpgrade: () => void;
plan: string;
}
const transformBytesToMB = (bytes: number) => {
return (bytes / 1024 / 1024).toFixed(2);
};
const transformBytesToGB = (bytes: number) => {
return (bytes / 1024 / 1024 / 1024).toFixed(2);
};
export const StorageProgress = ({
max: upperLimit,
value,
onUpgrade,
plan,
}: StorageProgressProgress) => {
const t = useAFFiNEI18N();
const percent = useMemo(
@ -31,51 +26,53 @@ export const StorageProgress = ({
[upperLimit, value]
);
const used = useMemo(() => transformBytesToMB(value), [value]);
const max = useMemo(() => transformBytesToGB(upperLimit), [upperLimit]);
const used = useMemo(() => bytes.format(value), [value]);
const max = useMemo(() => bytes.format(upperLimit), [upperLimit]);
const buttonType = useMemo(() => {
if (plan === 'Free') {
return 'primary';
}
return 'default';
}, [plan]);
return (
<>
<div className={styles.storageProgressContainer}>
<div className={styles.storageProgressWrapper}>
<div className="storage-progress-desc">
<span>{t['com.affine.storage.used.hint']()}</span>
<span>
{used}MB/{max}GB
</span>
</div>
<div className="storage-progress-bar-wrapper">
<div
className={clsx(styles.storageProgressBar, {
warning: percent > 80,
danger: percent > 99,
})}
style={{ width: `${percent}%` }}
></div>
</div>
</div>
<Tooltip content={t['com.affine.storage.disabled.hint']()}>
<span tabIndex={0}>
<Button disabled onClick={onUpgrade}>
{t['com.affine.storage.upgrade']()}
</Button>
<div className={styles.storageProgressContainer}>
<div className={styles.storageProgressWrapper}>
<div className="storage-progress-desc">
<span>{t['com.affine.storage.used.hint']()}</span>
<span>
{used}/{max}
{` (${plan} Plan)`}
</span>
</Tooltip>
</div>
{percent > 80 ? (
<div className={styles.storageExtendHint}>
{t['com.affine.storage.extend.hint']()}
<a
href="https://community.affine.pro/c/insider-general/"
target="_blank"
rel="noreferrer"
>
{t['com.affine.storage.extend.link']()}
</a>
</div>
) : null}
</>
<div className="storage-progress-bar-wrapper">
<div
className={clsx(styles.storageProgressBar, {
danger: percent > 80,
})}
style={{ width: `${percent > 100 ? '100' : percent}%` }}
></div>
</div>
</div>
<Tooltip
options={{ hidden: percent < 100 }}
content={
'You have reached the maximum capacity limit for your current account'
}
>
<span tabIndex={0}>
<Button
type={buttonType}
onClick={onUpgrade}
className={styles.storageButton}
>
{plan === 'Free' ? 'Upgrade' : 'Change'}
</Button>
</span>
</Tooltip>
</div>
);
};

View File

@ -43,6 +43,7 @@
"@react-hookz/web": "^23.1.0",
"@toeverything/components": "^0.0.45",
"async-call-rpc": "^6.3.1",
"bytes": "^3.1.2",
"css-spring": "^4.1.0",
"cssnano": "^6.0.1",
"graphql": "^16.8.1",
@ -75,6 +76,7 @@
"@sentry/webpack-plugin": "^2.8.0",
"@svgr/webpack": "^8.1.0",
"@swc/core": "^1.3.93",
"@types/bytes": "^3.1.3",
"@types/lodash-es": "^4.17.9",
"@types/webpack-env": "^1.18.2",
"copy-webpack-plugin": "^11.0.0",

View File

@ -7,6 +7,7 @@ import {
import {
allBlobSizesQuery,
removeAvatarMutation,
SubscriptionPlan,
uploadAvatarMutation,
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
@ -14,17 +15,24 @@ import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
import { Avatar } from '@toeverything/components/avatar';
import { Button } from '@toeverything/components/button';
import bytes from 'bytes';
import { useSetAtom } from 'jotai';
import {
type FC,
type MouseEvent,
Suspense,
useCallback,
useMemo,
useState,
} from 'react';
import { authAtom, openSignOutModalAtom } from '../../../../atoms';
import {
authAtom,
openSettingModalAtom,
openSignOutModalAtom,
} from '../../../../atoms';
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
import { useUserSubscription } from '../../../../hooks/use-subscription';
import { Upload } from '../../../pure/file-upload';
import * as style from './style.css';
@ -124,6 +132,7 @@ export const AvatarAndName = () => {
<Button
data-testid="save-user-name"
onClick={handleUpdateUserName}
className={style.button}
style={{
marginLeft: '12px',
}}
@ -146,7 +155,20 @@ const StoragePanel = () => {
query: allBlobSizesQuery,
});
const onUpgrade = useCallback(() => {}, []);
const [subscription] = useUserSubscription();
const plan = subscription?.plan ?? SubscriptionPlan.Free;
const maxLimit = useMemo(() => {
return bytes.parse(plan === SubscriptionPlan.Free ? '10GB' : '100GB');
}, [plan]);
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
const onUpgrade = useCallback(() => {
setSettingModalAtom({
open: true,
activeTab: 'plans',
workspaceId: null,
});
}, [setSettingModalAtom]);
return (
<SettingRow
@ -155,7 +177,8 @@ const StoragePanel = () => {
spreadCol={false}
>
<StorageProgress
max={10737418240}
max={maxLimit}
plan={plan}
value={data.collectAllBlobSizes.size}
onUpgrade={onUpgrade}
/>
@ -200,7 +223,7 @@ export const AccountSetting: FC = () => {
/>
<AvatarAndName />
<SettingRow name={t['com.affine.settings.email']()} desc={user.email}>
<Button onClick={onChangeEmail}>
<Button onClick={onChangeEmail} className={style.button}>
{t['com.affine.settings.email.action']()}
</Button>
</SettingRow>
@ -208,7 +231,7 @@ export const AccountSetting: FC = () => {
name={t['com.affine.settings.password']()}
desc={t['com.affine.settings.password.message']()}
>
<Button onClick={onPasswordButtonClick}>
<Button onClick={onPasswordButtonClick} className={style.button}>
{user.hasPassword
? t['com.affine.settings.password.action.change']()
: t['com.affine.settings.password.action.set']()}

View File

@ -39,3 +39,7 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
color: 'var(--affine-white)',
fontSize: 'var(--affine-font-h-4)',
});
export const button = style({
padding: '4px 12px',
});

View File

@ -223,12 +223,14 @@ __metadata:
"@toeverything/hooks": "workspace:*"
"@toeverything/infra": "workspace:*"
"@toeverything/theme": "npm:^0.7.20"
"@types/bytes": "npm:^3.1.3"
"@types/react": "npm:^18.2.28"
"@types/react-datepicker": "npm:^4.19.0"
"@types/react-dnd": "npm:^3.0.2"
"@types/react-dom": "npm:^18.2.13"
"@vanilla-extract/css": "npm:^1.13.0"
"@vanilla-extract/dynamic": "npm:^2.0.3"
bytes: "npm:^3.1.2"
check-password-strength: "npm:^2.0.7"
clsx: "npm:^2.0.0"
dayjs: "npm:^1.11.10"
@ -327,9 +329,11 @@ __metadata:
"@svgr/webpack": "npm:^8.1.0"
"@swc/core": "npm:^1.3.93"
"@toeverything/components": "npm:^0.0.45"
"@types/bytes": "npm:^3.1.3"
"@types/lodash-es": "npm:^4.17.9"
"@types/webpack-env": "npm:^1.18.2"
async-call-rpc: "npm:^6.3.1"
bytes: "npm:^3.1.2"
copy-webpack-plugin: "npm:^11.0.0"
css-loader: "npm:^6.8.1"
css-spring: "npm:^4.1.0"
@ -12856,6 +12860,13 @@ __metadata:
languageName: node
linkType: hard
"@types/bytes@npm:^3.1.3":
version: 3.1.3
resolution: "@types/bytes@npm:3.1.3"
checksum: c636b74d5a6f4925f1030382d8afcfe271e329da7c8103d175a874688118b60b0b1523e23477554e29456d024e5a180df1ba99d88c49b3a51433b714acffdac9
languageName: node
linkType: hard
"@types/cacheable-request@npm:^6.0.1":
version: 6.0.3
resolution: "@types/cacheable-request@npm:6.0.3"
@ -16310,7 +16321,7 @@ __metadata:
languageName: node
linkType: hard
"bytes@npm:3.1.2":
"bytes@npm:3.1.2, bytes@npm:^3.1.2":
version: 3.1.2
resolution: "bytes@npm:3.1.2"
checksum: a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388