feat(core): add sync paused dialog (#9135)
close AF-1932 AF-1954 AF-1953 AF-1955 Add a pop-up reminder when the workspace capacity exceeds the limit or the number of members exceeds the limit.
This commit is contained in:
@@ -109,6 +109,7 @@ export const WorkspaceSelector = ({
|
|||||||
showArrowDownIcon={showArrowDownIcon}
|
showArrowDownIcon={showArrowDownIcon}
|
||||||
disable={disable}
|
disable={disable}
|
||||||
hideCollaborationIcon={true}
|
hideCollaborationIcon={true}
|
||||||
|
hideTeamWorkspaceIcon={true}
|
||||||
data-testid="current-workspace-card"
|
data-testid="current-workspace-card"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ const TeamCard = () => {
|
|||||||
const expiration = teamSubscription?.canceledAt;
|
const expiration = teamSubscription?.canceledAt;
|
||||||
const nextBillingDate = teamSubscription?.nextBillAt;
|
const nextBillingDate = teamSubscription?.nextBillAt;
|
||||||
const recurring = teamSubscription?.recurring;
|
const recurring = teamSubscription?.recurring;
|
||||||
|
const endDate = teamSubscription?.end;
|
||||||
|
|
||||||
const description = useMemo(() => {
|
const description = useMemo(() => {
|
||||||
if (recurring === SubscriptionRecurring.Yearly) {
|
if (recurring === SubscriptionRecurring.Yearly) {
|
||||||
@@ -138,22 +139,22 @@ const TeamCard = () => {
|
|||||||
}, [recurring, t]);
|
}, [recurring, t]);
|
||||||
|
|
||||||
const expirationDate = useMemo(() => {
|
const expirationDate = useMemo(() => {
|
||||||
if (expiration) {
|
if (expiration && endDate) {
|
||||||
return t[
|
return t[
|
||||||
'com.affine.settings.workspace.billing.team-workspace.not-renewed'
|
'com.affine.settings.workspace.billing.team-workspace.not-renewed'
|
||||||
]({
|
]({
|
||||||
date: new Date(expiration).toLocaleDateString(),
|
date: new Date(endDate).toLocaleDateString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (nextBillingDate) {
|
if (nextBillingDate && endDate) {
|
||||||
return t[
|
return t[
|
||||||
'com.affine.settings.workspace.billing.team-workspace.next-billing-date'
|
'com.affine.settings.workspace.billing.team-workspace.next-billing-date'
|
||||||
]({
|
]({
|
||||||
date: new Date(nextBillingDate).toLocaleDateString(),
|
date: new Date(endDate).toLocaleDateString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}, [expiration, nextBillingDate, t]);
|
}, [endDate, expiration, nextBillingDate, t]);
|
||||||
|
|
||||||
const amount = teamSubscription
|
const amount = teamSubscription
|
||||||
? teamPrices && workspaceMemberCount
|
? teamPrices && workspaceMemberCount
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
permissionService.permission.revalidate();
|
permissionService.permission.revalidate();
|
||||||
}, [permissionService]);
|
}, [permissionService]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
membersService.members.revalidate();
|
||||||
|
}, [membersService]);
|
||||||
|
|
||||||
const workspaceQuotaService = useService(WorkspaceQuotaService);
|
const workspaceQuotaService = useService(WorkspaceQuotaService);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
workspaceQuotaService.quota.revalidate();
|
workspaceQuotaService.quota.revalidate();
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const MemberItem = ({
|
|||||||
const show = isOwner && currentAccount.id !== member.id;
|
const show = isOwner && currentAccount.id !== member.id;
|
||||||
|
|
||||||
const handleOpenAssignModal = useCallback(() => {
|
const handleOpenAssignModal = useCallback(() => {
|
||||||
|
setInputValue('');
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ export const MemberOptions = ({
|
|||||||
onClick: handleDecline,
|
onClick: handleDecline,
|
||||||
show:
|
show:
|
||||||
(isAdmin || isOwner) &&
|
(isAdmin || isOwner) &&
|
||||||
member.status === WorkspaceMemberStatus.UnderReview,
|
(member.status === WorkspaceMemberStatus.UnderReview ||
|
||||||
|
member.status === WorkspaceMemberStatus.NeedMoreSeatAndReview),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t['com.affine.payment.member.team.revoke'](),
|
label: t['com.affine.payment.member.team.revoke'](),
|
||||||
@@ -207,7 +208,8 @@ export const MemberOptions = ({
|
|||||||
onClick: handleChangeToAdmin,
|
onClick: handleChangeToAdmin,
|
||||||
show:
|
show:
|
||||||
isOwner &&
|
isOwner &&
|
||||||
member.permission === Permission.Write &&
|
member.permission !== Permission.Owner &&
|
||||||
|
member.permission !== Permission.Admin &&
|
||||||
member.status === WorkspaceMemberStatus.Accepted,
|
member.status === WorkspaceMemberStatus.Accepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,7 +100,11 @@ export const UpgradeToTeam = ({ recurring }: { recurring: string | null }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuTrigger className={styles.menuTrigger} tooltip={menuTriggerText}>
|
<MenuTrigger
|
||||||
|
className={styles.menuTrigger}
|
||||||
|
tooltip={menuTriggerText}
|
||||||
|
data-selected={!!selectedWorkspace}
|
||||||
|
>
|
||||||
{menuTriggerText}
|
{menuTriggerText}
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const menuTrigger = style({
|
|||||||
fontSize: cssVar('fontBase'),
|
fontSize: cssVar('fontBase'),
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: cssVarV2('text/placeholder'),
|
color: cssVarV2('text/placeholder'),
|
||||||
|
selectors: {
|
||||||
|
'&[data-selected="true"]': {
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const upgradeButton = style({
|
export const upgradeButton = style({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { AIIsland } from '@affine/core/desktop/components/ai-island';
|
|||||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
||||||
import { WorkspaceDialogs } from '@affine/core/desktop/dialogs';
|
import { WorkspaceDialogs } from '@affine/core/desktop/dialogs';
|
||||||
import { PeekViewManagerModal } from '@affine/core/modules/peek-view';
|
import { PeekViewManagerModal } from '@affine/core/modules/peek-view';
|
||||||
|
import { QuotaCheck } from '@affine/core/modules/quota';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import {
|
import {
|
||||||
LiveData,
|
LiveData,
|
||||||
@@ -31,7 +32,10 @@ export const WorkspaceLayout = function WorkspaceLayout({
|
|||||||
{currentWorkspace?.flavour === 'local' ? (
|
{currentWorkspace?.flavour === 'local' ? (
|
||||||
<LocalQuotaModal />
|
<LocalQuotaModal />
|
||||||
) : (
|
) : (
|
||||||
<CloudQuotaModal />
|
<>
|
||||||
|
<CloudQuotaModal />
|
||||||
|
<QuotaCheck workspaceMeta={currentWorkspace.meta} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<AiLoginRequiredModal />
|
<AiLoginRequiredModal />
|
||||||
<WorkspaceSideEffects />
|
<WorkspaceSideEffects />
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ export class WorkspacePermission extends Entity {
|
|||||||
permission: Permission,
|
permission: Permission,
|
||||||
sendInviteMail?: boolean
|
sendInviteMail?: boolean
|
||||||
) {
|
) {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to invite members');
|
|
||||||
}
|
|
||||||
return await this.store.inviteMember(
|
return await this.store.inviteMember(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
email,
|
email,
|
||||||
@@ -92,9 +89,6 @@ export class WorkspacePermission extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async inviteMembers(emails: string[], sendInviteMail?: boolean) {
|
async inviteMembers(emails: string[], sendInviteMail?: boolean) {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to invite members');
|
|
||||||
}
|
|
||||||
return await this.store.inviteBatch(
|
return await this.store.inviteBatch(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
emails,
|
emails,
|
||||||
@@ -103,9 +97,6 @@ export class WorkspacePermission extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async generateInviteLink(expireTime: WorkspaceInviteLinkExpireTime) {
|
async generateInviteLink(expireTime: WorkspaceInviteLinkExpireTime) {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to generate invite link');
|
|
||||||
}
|
|
||||||
return await this.store.generateInviteLink(
|
return await this.store.generateInviteLink(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
expireTime
|
expireTime
|
||||||
@@ -113,18 +104,12 @@ export class WorkspacePermission extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async revokeInviteLink() {
|
async revokeInviteLink() {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to revoke invite link');
|
|
||||||
}
|
|
||||||
return await this.store.revokeInviteLink(
|
return await this.store.revokeInviteLink(
|
||||||
this.workspaceService.workspace.id
|
this.workspaceService.workspace.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async revokeMember(userId: string) {
|
async revokeMember(userId: string) {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to revoke members');
|
|
||||||
}
|
|
||||||
return await this.store.revokeMemberPermission(
|
return await this.store.revokeMemberPermission(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
userId
|
userId
|
||||||
@@ -140,9 +125,6 @@ export class WorkspacePermission extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async approveMember(userId: string) {
|
async approveMember(userId: string) {
|
||||||
if (!this.isAdmin$.value && !this.isOwner$.value) {
|
|
||||||
throw new Error('User has no permission to accept invite');
|
|
||||||
}
|
|
||||||
return await this.store.approveMember(
|
return await this.store.approveMember(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
userId
|
userId
|
||||||
@@ -150,9 +132,6 @@ export class WorkspacePermission extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async adjustMemberPermission(userId: string, permission: Permission) {
|
async adjustMemberPermission(userId: string, permission: Permission) {
|
||||||
if (!this.isAdmin$.value) {
|
|
||||||
throw new Error('User has no permission to adjust member permissions');
|
|
||||||
}
|
|
||||||
return await this.store.adjustMemberPermission(
|
return await this.store.adjustMemberPermission(
|
||||||
this.workspaceService.workspace.id,
|
this.workspaceService.workspace.id,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { WorkspaceQuotaService } from './services/quota';
|
export { WorkspaceQuotaService } from './services/quota';
|
||||||
|
export { QuotaCheck } from './views/quota-check';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type Framework,
|
type Framework,
|
||||||
|
|||||||
192
packages/frontend/core/src/modules/quota/views/quota-check.tsx
Normal file
192
packages/frontend/core/src/modules/quota/views/quota-check.tsx
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { useConfirmModal } from '@affine/component';
|
||||||
|
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||||
|
import { type I18nString, useI18n } from '@affine/i18n';
|
||||||
|
import {
|
||||||
|
useLiveData,
|
||||||
|
useService,
|
||||||
|
type WorkspaceMetadata,
|
||||||
|
WorkspacesService,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { WorkspaceQuotaService } from '../services/quota';
|
||||||
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
title: I18nString;
|
||||||
|
description: I18nString;
|
||||||
|
confirmText: I18nString;
|
||||||
|
tips?: I18nString[];
|
||||||
|
cancelText?: I18nString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Notification that the cloud workspace quota has exceeded the limit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const QuotaCheck = ({
|
||||||
|
workspaceMeta,
|
||||||
|
}: {
|
||||||
|
workspaceMeta: WorkspaceMetadata;
|
||||||
|
}) => {
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
const workspacesService = useService(WorkspacesService);
|
||||||
|
const workspaceQuota = useService(WorkspaceQuotaService).quota;
|
||||||
|
const workspaceProfile = workspacesService.getProfile(workspaceMeta);
|
||||||
|
const quota = useLiveData(workspaceQuota.quota$);
|
||||||
|
const usedPercent = useLiveData(workspaceQuota.percent$);
|
||||||
|
const isOwner = useLiveData(workspaceProfile.profile$)?.isOwner;
|
||||||
|
const globalDialogService = useService(GlobalDialogService);
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
const onConfirm = useCallback(() => {
|
||||||
|
if (!isOwner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
globalDialogService.open('setting', {
|
||||||
|
activeTab: 'plans',
|
||||||
|
scrollAnchor: 'cloudPricingPlan',
|
||||||
|
});
|
||||||
|
}, [globalDialogService, isOwner]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
workspaceQuota?.revalidate();
|
||||||
|
}, [workspaceQuota]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workspaceMeta.flavour === 'local' || !quota) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const memberOverflow = quota.memberCount > quota.memberLimit;
|
||||||
|
// remember to use real percent
|
||||||
|
const storageOverflow = usedPercent && usedPercent >= 100;
|
||||||
|
const message = getSyncPausedMessage(
|
||||||
|
!!isOwner,
|
||||||
|
memberOverflow,
|
||||||
|
!!storageOverflow
|
||||||
|
);
|
||||||
|
|
||||||
|
if (memberOverflow || storageOverflow) {
|
||||||
|
openConfirmModal({
|
||||||
|
title: t.t(message.title),
|
||||||
|
description: t.t(message.description),
|
||||||
|
confirmText: t.t(message.confirmText),
|
||||||
|
cancelText: message.cancelText ? t.t(message.cancelText) : undefined,
|
||||||
|
children: <Tips tips={message.tips} />,
|
||||||
|
childrenContentClassName: styles.modalChildren,
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
cancelButtonOptions: {
|
||||||
|
style: {
|
||||||
|
visibility: message.cancelText ? 'visible' : 'hidden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isOwner,
|
||||||
|
onConfirm,
|
||||||
|
openConfirmModal,
|
||||||
|
quota,
|
||||||
|
t,
|
||||||
|
usedPercent,
|
||||||
|
workspaceMeta.flavour,
|
||||||
|
]);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages: Record<
|
||||||
|
'owner' | 'member',
|
||||||
|
Record<'both' | 'storage' | 'member', Message>
|
||||||
|
> = {
|
||||||
|
owner: {
|
||||||
|
both: {
|
||||||
|
title: 'com.affine.payment.sync-paused.owner.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.owner.both.description',
|
||||||
|
tips: [
|
||||||
|
'com.affine.payment.sync-paused.owner.both.tips-1',
|
||||||
|
'com.affine.payment.sync-paused.owner.both.tips-2',
|
||||||
|
],
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
confirmText: 'com.affine.payment.upgrade',
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
title: 'com.affine.payment.sync-paused.owner.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.owner.storage.description',
|
||||||
|
tips: [
|
||||||
|
'com.affine.payment.sync-paused.owner.storage.tips-1',
|
||||||
|
'com.affine.payment.sync-paused.owner.storage.tips-2',
|
||||||
|
],
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
confirmText: 'com.affine.payment.upgrade',
|
||||||
|
},
|
||||||
|
member: {
|
||||||
|
title: 'com.affine.payment.sync-paused.owner.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.owner.member.description',
|
||||||
|
tips: [
|
||||||
|
'com.affine.payment.sync-paused.owner.member.tips-1',
|
||||||
|
'com.affine.payment.sync-paused.owner.member.tips-2',
|
||||||
|
],
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
confirmText: 'com.affine.payment.upgrade',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
member: {
|
||||||
|
both: {
|
||||||
|
title: 'com.affine.payment.sync-paused.member.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.member.both.description',
|
||||||
|
confirmText: 'com.affine.payment.sync-paused.member.member.confirm',
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
title: 'com.affine.payment.sync-paused.member.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.member.storage.description',
|
||||||
|
confirmText: 'com.affine.payment.sync-paused.member.member.confirm',
|
||||||
|
},
|
||||||
|
member: {
|
||||||
|
title: 'com.affine.payment.sync-paused.member.title',
|
||||||
|
description: 'com.affine.payment.sync-paused.member.member.description',
|
||||||
|
confirmText: 'com.affine.payment.sync-paused.member.member.confirm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSyncPausedMessage(
|
||||||
|
isOwner: boolean,
|
||||||
|
isMemberOverflow: boolean,
|
||||||
|
isStorageOverflow: boolean
|
||||||
|
): Message {
|
||||||
|
const userType = isOwner ? 'owner' : 'member';
|
||||||
|
const condition =
|
||||||
|
isStorageOverflow && isMemberOverflow
|
||||||
|
? 'both'
|
||||||
|
: isStorageOverflow
|
||||||
|
? 'storage'
|
||||||
|
: isMemberOverflow
|
||||||
|
? 'member'
|
||||||
|
: 'both';
|
||||||
|
|
||||||
|
return messages[userType][condition];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tips = ({ tips }: { tips?: I18nString[] }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
if (!tips || tips.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.tipsStyle}>
|
||||||
|
{tips.map(tip => (
|
||||||
|
<div key={tip.toString()} className={styles.tipStyle}>
|
||||||
|
<div className={styles.bullet} />
|
||||||
|
{t.t(tip)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
27
packages/frontend/core/src/modules/quota/views/styles.css.ts
Normal file
27
packages/frontend/core/src/modules/quota/views/styles.css.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const tipsStyle = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tipStyle = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const bullet = style({
|
||||||
|
backgroundColor: cssVarV2('icon/activated'),
|
||||||
|
width: '6px',
|
||||||
|
height: '6px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
marginTop: '8px',
|
||||||
|
marginLeft: '4px',
|
||||||
|
marginRight: '12px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modalChildren = style({
|
||||||
|
paddingLeft: '0',
|
||||||
|
});
|
||||||
@@ -5,20 +5,20 @@
|
|||||||
"de": 26,
|
"de": 26,
|
||||||
"el-GR": 0,
|
"el-GR": 0,
|
||||||
"en": 100,
|
"en": 100,
|
||||||
"es-AR": 13,
|
"es-AR": 12,
|
||||||
"es-CL": 14,
|
"es-CL": 14,
|
||||||
"es": 12,
|
"es": 12,
|
||||||
"fr": 61,
|
"fr": 60,
|
||||||
"hi": 2,
|
"hi": 2,
|
||||||
"it-IT": 1,
|
"it-IT": 1,
|
||||||
"it": 1,
|
"it": 1,
|
||||||
"ja": 91,
|
"ja": 90,
|
||||||
"ko": 73,
|
"ko": 72,
|
||||||
"pl": 0,
|
"pl": 0,
|
||||||
"pt-BR": 78,
|
"pt-BR": 78,
|
||||||
"ru": 67,
|
"ru": 66,
|
||||||
"sv-SE": 4,
|
"sv-SE": 4,
|
||||||
"ur": 2,
|
"ur": 2,
|
||||||
"zh-Hans": 91,
|
"zh-Hans": 91,
|
||||||
"zh-Hant": 91
|
"zh-Hant": 90
|
||||||
}
|
}
|
||||||
@@ -1600,5 +1600,20 @@
|
|||||||
"com.affine.upgrade-to-team-page.no-workspace-available": "No workspace available",
|
"com.affine.upgrade-to-team-page.no-workspace-available": "No workspace available",
|
||||||
"com.affine.workspace.storage": "Workspace storage",
|
"com.affine.workspace.storage": "Workspace storage",
|
||||||
"com.affine.cmdk.affine.category.affine.journal": "Journal",
|
"com.affine.cmdk.affine.category.affine.journal": "Journal",
|
||||||
"com.affine.cmdk.affine.category.affine.date-picker": "Select a specific date"
|
"com.affine.cmdk.affine.category.affine.date-picker": "Select a specific date",
|
||||||
|
"com.affine.payment.sync-paused.owner.title": "[Action Required] Workspace sync paused",
|
||||||
|
"com.affine.payment.sync-paused.owner.both.description": "Your workspace has exceeded both storage and member limits, causing synchronization to pause. To resume syncing, please either:",
|
||||||
|
"com.affine.payment.sync-paused.owner.both.tips-1": "Reduce storage usage and remove some team members",
|
||||||
|
"com.affine.payment.sync-paused.owner.both.tips-2": "Upgrade your plan for increased capacity",
|
||||||
|
"com.affine.payment.sync-paused.owner.storage.description": "Your workspace has exceeded its storage limit and synchronization has been paused. To resume syncing, please either:",
|
||||||
|
"com.affine.payment.sync-paused.owner.storage.tips-1": "Remove unnecessary files or content to reduce storage usage",
|
||||||
|
"com.affine.payment.sync-paused.owner.storage.tips-2": "Upgrade your plan for increased storage capacity",
|
||||||
|
"com.affine.payment.sync-paused.owner.member.description": "Your workspace has reached its maximum member capacity and synchronization has been paused. To resume syncing, you can either",
|
||||||
|
"com.affine.payment.sync-paused.owner.member.tips-1": "Remove some team members from the workspace",
|
||||||
|
"com.affine.payment.sync-paused.owner.member.tips-2": "Upgrade your plan to accommodate more members",
|
||||||
|
"com.affine.payment.sync-paused.member.title": "Workspace sync paused",
|
||||||
|
"com.affine.payment.sync-paused.member.both.description": "This workspace has exceeded both storage and member limits, causing synchronization to pause. Please contact your workspace owner to address these limits and resume syncing.",
|
||||||
|
"com.affine.payment.sync-paused.member.storage.description": "This workspace has exceeded its storage limit and synchronization has been paused. Please contact your workspace owner to either reduce storage usage or upgrade the plan to resume syncing.",
|
||||||
|
"com.affine.payment.sync-paused.member.member.description": "This workspace has reached its maximum member capacity and synchronization has been paused. Please contact your workspace owner to either adjust team membership or upgrade the plan to resume syncing.",
|
||||||
|
"com.affine.payment.sync-paused.member.member.confirm": "Got It"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ test('should have pagination in member list', async ({ page }) => {
|
|||||||
|
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
await page.getByTestId('confirm-modal-cancel').click();
|
||||||
|
|
||||||
const firstPageMemberItemCount = await page
|
const firstPageMemberItemCount = await page
|
||||||
.locator('[data-testid="member-item"]')
|
.locator('[data-testid="member-item"]')
|
||||||
.count();
|
.count();
|
||||||
|
|||||||
Reference in New Issue
Block a user