mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(core): add workspace quota panel for team workspace (#9085)
close AF-1917 AF-1685 AF-1730
This commit is contained in:
@@ -103,7 +103,7 @@ const getSignUpText = (
|
||||
case SubscriptionPlan.Free:
|
||||
return t['com.affine.payment.sign-up-free']();
|
||||
case SubscriptionPlan.Team:
|
||||
return t['com.affine.payment.start-free-trial']();
|
||||
return t['com.affine.payment.upgrade']();
|
||||
default:
|
||||
return t['com.affine.payment.buy-pro']();
|
||||
}
|
||||
@@ -263,7 +263,7 @@ const UpgradeToTeam = () => {
|
||||
variant="primary"
|
||||
data-event-args-url={url}
|
||||
>
|
||||
{t['com.affine.payment.start-free-trial']()}
|
||||
{t['com.affine.payment.upgrade']()}
|
||||
</Button>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Button } from '@affine/component/ui/button';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
|
||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import {
|
||||
useLiveData,
|
||||
useService,
|
||||
type Workspace,
|
||||
type WorkspaceMetadata,
|
||||
@@ -23,6 +25,13 @@ export const DesktopExportPanel = ({
|
||||
workspace,
|
||||
}: ExportPanelProps) => {
|
||||
const workspaceId = workspaceMetadata.id;
|
||||
const workspacePermissionService = useService(
|
||||
WorkspacePermissionService
|
||||
).permission;
|
||||
const isTeam = useLiveData(workspacePermissionService.isTeam$);
|
||||
const isOwner = useLiveData(workspacePermissionService.isOwner$);
|
||||
const isAdmin = useLiveData(workspacePermissionService.isAdmin$);
|
||||
|
||||
const t = useI18n();
|
||||
const [saving, setSaving] = useState(false);
|
||||
const isOnline = useSystemOnline();
|
||||
@@ -55,6 +64,10 @@ export const DesktopExportPanel = ({
|
||||
}
|
||||
}, [desktopApi, isOnline, saving, t, workspace, workspaceId]);
|
||||
|
||||
if (isTeam && !isOwner && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
||||
<Button
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MembersPanel } from './members';
|
||||
import { ProfilePanel } from './profile';
|
||||
import { SharingPanel } from './sharing';
|
||||
import type { WorkspaceSettingDetailProps } from './types';
|
||||
import { WorkspaceQuotaPanel } from './workspace-quota';
|
||||
|
||||
export const WorkspaceSettingDetail = ({
|
||||
workspaceMetadata,
|
||||
@@ -69,6 +70,7 @@ export const WorkspaceSettingDetail = ({
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
|
||||
<EnableCloudPanel onCloseSetting={onCloseSetting} />
|
||||
<WorkspaceQuotaPanel />
|
||||
<MembersPanel onChangeSettingState={onChangeSettingState} />
|
||||
</SettingWrapper>
|
||||
<AiSetting />
|
||||
|
||||
@@ -146,7 +146,7 @@ export const CloudWorkspaceMembersPanel = ({
|
||||
return (
|
||||
<span>
|
||||
{t['com.affine.payment.member.description2']()}
|
||||
{hasPaymentFeature ? (
|
||||
{hasPaymentFeature && isOwner ? (
|
||||
<div
|
||||
className={styles.goUpgradeWrapper}
|
||||
onClick={handleUpgradeConfirm}
|
||||
@@ -158,7 +158,14 @@ export const CloudWorkspaceMembersPanel = ({
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}, [handleUpgradeConfirm, hasPaymentFeature, isTeam, t, workspaceQuota]);
|
||||
}, [
|
||||
handleUpgradeConfirm,
|
||||
hasPaymentFeature,
|
||||
isOwner,
|
||||
isTeam,
|
||||
t,
|
||||
workspaceQuota,
|
||||
]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (isTeam) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
export const profileWrapper = style({
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
@@ -35,3 +35,31 @@ export const workspaceLabel = style({
|
||||
lineHeight: '20px',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const storageProgressContainer = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
export const storageProgressWrapper = style({
|
||||
flexGrow: 1,
|
||||
marginRight: '20px',
|
||||
});
|
||||
globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
|
||||
fontSize: cssVar('fontXs'),
|
||||
color: cssVarV2('text/secondary'),
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 2,
|
||||
});
|
||||
globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
|
||||
height: '8px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||
overflow: 'hidden',
|
||||
});
|
||||
export const storageProgressBar = style({
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { ErrorMessage, Skeleton } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import * as styles from './style.css';
|
||||
|
||||
export const WorkspaceQuotaPanel = () => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<SettingRow
|
||||
name={t['com.affine.workspace.storage']()}
|
||||
desc=""
|
||||
spreadCol={false}
|
||||
>
|
||||
<StorageProgress />
|
||||
</SettingRow>
|
||||
);
|
||||
};
|
||||
|
||||
export const StorageProgress = () => {
|
||||
const t = useI18n();
|
||||
const workspacePermissionService = useService(
|
||||
WorkspacePermissionService
|
||||
).permission;
|
||||
const workspaceQuotaService = useService(WorkspaceQuotaService).quota;
|
||||
const isTeam = useLiveData(workspacePermissionService.isTeam$);
|
||||
const isLoading = useLiveData(workspaceQuotaService.isLoading$);
|
||||
const usedFormatted = useLiveData(workspaceQuotaService.usedFormatted$);
|
||||
const maxFormatted = useLiveData(workspaceQuotaService.maxFormatted$);
|
||||
const percent = useLiveData(workspaceQuotaService.percent$);
|
||||
const color = useLiveData(workspaceQuotaService.color$);
|
||||
|
||||
useEffect(() => {
|
||||
// revalidate quota to get the latest status
|
||||
workspaceQuotaService.revalidate();
|
||||
}, [workspaceQuotaService]);
|
||||
|
||||
const loadError = useLiveData(workspaceQuotaService.error$);
|
||||
|
||||
if (isLoading) {
|
||||
if (loadError) {
|
||||
return <ErrorMessage>Load error</ErrorMessage>;
|
||||
}
|
||||
return <Skeleton height={42} />;
|
||||
}
|
||||
|
||||
if (!isTeam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.storageProgressContainer}>
|
||||
<div className={styles.storageProgressWrapper}>
|
||||
<div className="storage-progress-desc">
|
||||
<span>{t['com.affine.storage.used.hint']()}</span>
|
||||
<span>
|
||||
{usedFormatted}/{maxFormatted}
|
||||
</span>
|
||||
</div>
|
||||
<div className="storage-progress-bar-wrapper">
|
||||
<div
|
||||
className={styles.storageProgressBar}
|
||||
style={{
|
||||
width: `${percent}%`,
|
||||
backgroundColor: color ?? cssVarV2('toast/iconState/regular'),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user