diff --git a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
index 36190de6a..b376ecd0d 100644
--- a/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
+++ b/packages/frontend/core/src/components/root-app-sidebar/user-info.tsx
@@ -14,7 +14,7 @@ import {
} from '@affine/core/modules/dialogs';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
-import { AccountIcon, SignOutIcon } from '@blocksuite/icons/rc';
+import { AccountIcon, AdminIcon, SignOutIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { assignInlineVars } from '@vanilla-extract/dynamic';
@@ -27,6 +27,7 @@ import {
ServerService,
SubscriptionService,
UserCopilotQuotaService,
+ UserFeatureService,
UserQuotaService,
} from '../../modules/cloud';
import { UserPlanButton } from '../affine/auth/user-plan-button';
@@ -80,6 +81,9 @@ const UnauthorizedUserInfo = () => {
const AccountMenu = () => {
const workspaceDialogService = useService(WorkspaceDialogService);
const openSignOutModal = useSignOut();
+ const serverService = useService(ServerService);
+ const userFeatureService = useService(UserFeatureService);
+ const isAdmin = useLiveData(userFeatureService.userFeature.isAdmin$);
const onOpenAccountSetting = useCallback(() => {
track.$.navigationPanel.profileAndBadge.openSettings({ to: 'account' });
@@ -88,8 +92,16 @@ const AccountMenu = () => {
});
}, [workspaceDialogService]);
+ const onOpenAdminPanel = useCallback(() => {
+ window.open(`${serverService.server.baseUrl}/admin`, '_blank');
+ }, [serverService.server.baseUrl]);
+
const t = useI18n();
+ useEffect(() => {
+ userFeatureService.userFeature.revalidate();
+ }, [userFeatureService]);
+
return (
<>
+ {isAdmin ? (
+ }
+ data-testid="workspace-modal-account-admin-option"
+ onClick={onOpenAdminPanel}
+ >
+ {t['com.affine.workspace.cloud.account.admin']()}
+
+ ) : null}
}
data-testid="workspace-modal-sign-out-option"
diff --git a/packages/frontend/core/src/modules/cloud/entities/user-feature.ts b/packages/frontend/core/src/modules/cloud/entities/user-feature.ts
index 967ab4449..d1e44126d 100644
--- a/packages/frontend/core/src/modules/cloud/entities/user-feature.ts
+++ b/packages/frontend/core/src/modules/cloud/entities/user-feature.ts
@@ -18,6 +18,11 @@ import type { UserFeatureStore } from '../stores/user-feature';
export class UserFeature extends Entity {
// undefined means no user, null means loading
features$ = new LiveData(null);
+
+ isAdmin$ = this.features$.map(features =>
+ features === null ? null : features?.some(f => f === FeatureType.Admin)
+ );
+
isEarlyAccess$ = this.features$.map(features =>
features === null
? null
diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json
index 51ac8b71d..39b698a73 100644
--- a/packages/frontend/i18n/src/i18n-completenesses.json
+++ b/packages/frontend/i18n/src/i18n-completenesses.json
@@ -14,7 +14,7 @@
"it-IT": 99,
"it": 1,
"ja": 99,
- "ko": 63,
+ "ko": 62,
"pl": 99,
"pt-BR": 99,
"ru": 99,
diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts
index 65a0c1236..383cb725e 100644
--- a/packages/frontend/i18n/src/i18n.gen.ts
+++ b/packages/frontend/i18n/src/i18n.gen.ts
@@ -5404,8 +5404,8 @@ export function useAFFiNEI18N(): {
*/
["com.affine.settings.workspace.experimental-features.enable-callout.description"](): string;
/**
- * Embed Iframe Block
- */
+ * `Embed Iframe Block`
+ */
["com.affine.settings.workspace.experimental-features.enable-embed-iframe-block.name"](): string;
/**
* `Enables Embed Iframe Block.`
@@ -6465,6 +6465,10 @@ export function useAFFiNEI18N(): {
* `Account settings`
*/
["com.affine.workspace.cloud.account.settings"](): string;
+ /**
+ * `Admin panel`
+ */
+ ["com.affine.workspace.cloud.account.admin"](): string;
/**
* `Sign up/ Sign in`
*/
@@ -6549,6 +6553,18 @@ export function useAFFiNEI18N(): {
* `Yesterday`
*/
["com.affine.yesterday"](): string;
+ /**
+ * `Inactive`
+ */
+ ["com.affine.inactive"](): string;
+ /**
+ * `Inactive member`
+ */
+ ["com.affine.inactive-member"](): string;
+ /**
+ * `Inactive workspace`
+ */
+ ["com.affine.inactive-workspace"](): string;
/**
* `core`
*/
@@ -6992,12 +7008,9 @@ export function useAFFiNEI18N(): {
*/
["com.affine.notification.unsupported"](): string;
/**
- * `{{username}} mentioned you in {{docTitle}}`
+ * `No new notifications`
*/
- ["com.affine.notification.mention"](options: Readonly<{
- username: string;
- docTitle: string;
- }>): string;
+ ["com.affine.notification.empty"](): string;
/**
* `Tips`
*/
@@ -7006,6 +7019,10 @@ export function useAFFiNEI18N(): {
* `Template`
*/
Template(): string;
+ /**
+ * `Delete Template`
+ */
+ ["com.affine.template-list.delete"](): string;
/**
* `No template`
*/
@@ -7119,6 +7136,12 @@ export function useAFFiNEI18N(): {
["error.QUERY_TOO_LONG"](options: {
readonly max: string;
}): string;
+ /**
+ * `Validation error, errors: {{errors}}`
+ */
+ ["error.VALIDATION_ERROR"](options: {
+ readonly errors: string;
+ }): string;
/**
* `User not found.`
*/
@@ -7162,6 +7185,10 @@ export function useAFFiNEI18N(): {
status: string;
body: string;
}>): string;
+ /**
+ * `Invalid auth state. You might start the auth progress from another device.`
+ */
+ ["error.INVALID_AUTH_STATE"](): string;
/**
* `Missing query parameter `{{name}}`.`
*/
@@ -7622,21 +7649,15 @@ export function useAFFiNEI18N(): {
*/
["error.NOTIFICATION_NOT_FOUND"](): string;
/**
- * `Mention user do not have permission to access space {{spaceId}}.`
+ * `Mentioned user can not access doc {{docId}}.`
*/
- ["error.MENTION_USER_SPACE_ACCESS_DENIED"](options: {
- readonly spaceId: string;
+ ["error.MENTION_USER_DOC_ACCESS_DENIED"](options: {
+ readonly docId: string;
}): string;
/**
- * `You cannot mention yourself.`
+ * `You can not mention yourself.`
*/
["error.MENTION_USER_ONESELF_DENIED"](): string;
- /**
- * `You do not have permission to access notification {{notificationId}}.`
- */
- ["error.NOTIFICATION_ACCESS_DENIED"](options: {
- readonly notificationId: string;
- }): string;
} { const { t } = useTranslation(); return useMemo(() => createProxy((key) => t.bind(null, key)), [t]); }
function createComponent(i18nKey: string) {
return (props) => createElement(Trans, { i18nKey, shouldUnescape: true, ...props });
@@ -8061,6 +8082,16 @@ export const TypedTrans: {
}, {
["1"]: JSX.Element;
}>>;
+ /**
+ * `<1>{{username}}1> mentioned you in <2>{{docTitle}}2>`
+ */
+ ["com.affine.notification.mention"]: ComponentType, {
+ ["1"]: JSX.Element;
+ ["2"]: JSX.Element;
+ }>>;
/**
* `Unable to join <1/> <2>{{workspaceName}}2> due to insufficient seats available.`
*/
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index b423cb351..de968f15d 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -1610,6 +1610,7 @@
"com.affine.workspace.cloud": "Cloud workspaces",
"com.affine.workspace.cloud.account.logout": "Sign out",
"com.affine.workspace.cloud.account.settings": "Account settings",
+ "com.affine.workspace.cloud.account.admin": "Admin panel",
"com.affine.workspace.cloud.auth": "Sign up/ Sign in",
"com.affine.workspace.cloud.description": "Sync with AFFiNE Cloud",
"com.affine.workspace.cloud.join": "Join workspace",
@@ -1774,6 +1775,7 @@
"error.BAD_REQUEST": "Bad request.",
"error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}",
"error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.",
+ "error.VALIDATION_ERROR": "Validation error, errors: {{errors}}",
"error.USER_NOT_FOUND": "User not found.",
"error.USER_AVATAR_NOT_FOUND": "User avatar not found.",
"error.EMAIL_ALREADY_USED": "This email has already been registered.",
@@ -1783,6 +1785,7 @@
"error.OAUTH_STATE_EXPIRED": "OAuth state expired, please try again.",
"error.INVALID_OAUTH_CALLBACK_STATE": "Invalid callback state parameter.",
"error.INVALID_OAUTH_CALLBACK_CODE": "Invalid callback code parameter, provider response status: {{status}} and body: {{body}}.",
+ "error.INVALID_AUTH_STATE": "Invalid auth state. You might start the auth progress from another device.",
"error.MISSING_OAUTH_QUERY_PARAMETER": "Missing query parameter `{{name}}`.",
"error.OAUTH_ACCOUNT_ALREADY_CONNECTED": "The third-party account has already been connected to another user.",
"error.INVALID_EMAIL": "An invalid email provided: {{email}}",
@@ -1875,7 +1878,6 @@
"error.WORKSPACE_MEMBERS_EXCEED_LIMIT_TO_DOWNGRADE": "You cannot downgrade the workspace from team workspace because there are more than {{limit}} members that are currently active.",
"error.UNSUPPORTED_CLIENT_VERSION": "Unsupported client with version [{{clientVersion}}], required version is [{{requiredVersion}}].",
"error.NOTIFICATION_NOT_FOUND": "Notification not found.",
- "error.MENTION_USER_SPACE_ACCESS_DENIED": "Mention user do not have permission to access space {{spaceId}}.",
- "error.MENTION_USER_ONESELF_DENIED": "You cannot mention yourself.",
- "error.NOTIFICATION_ACCESS_DENIED": "You do not have permission to access notification {{notificationId}}."
+ "error.MENTION_USER_DOC_ACCESS_DENIED": "Mentioned user can not access doc {{docId}}.",
+ "error.MENTION_USER_ONESELF_DENIED": "You can not mention yourself."
}