From 36eb4991c968d8898a408c8dd52fce1d61056cc5 Mon Sep 17 00:00:00 2001 From: EYHN Date: Tue, 25 Mar 2025 14:51:08 +0800 Subject: [PATCH] feat(core): add more notification types (#11156) --- .../infra/src/framework/react/index.tsx | 3 +- .../desktop/notification-card.tsx | 11 +- .../component/src/ui/notification/notify.tsx | 12 +- .../component/src/ui/notification/types.ts | 2 + .../components/hooks/use-navigate-helper.ts | 22 ++ .../components/notification/list.style.css.ts | 9 + .../core/src/components/notification/list.tsx | 371 ++++++++++++++++-- .../desktop/dialogs/change-password/index.tsx | 12 +- .../core/src/desktop/pages/invite/index.tsx | 14 +- .../src/desktop/pages/workspace/index.tsx | 11 +- .../pages/workspace/settings/index.tsx | 29 ++ .../core/src/desktop/workbench-router.ts | 4 + .../modules/cloud/services/accept-invite.ts | 26 +- .../i18n/src/i18n-completenesses.json | 2 +- packages/frontend/i18n/src/i18n.gen.ts | 34 ++ packages/frontend/i18n/src/resources/en.json | 5 + 16 files changed, 510 insertions(+), 57 deletions(-) create mode 100644 packages/frontend/core/src/desktop/pages/workspace/settings/index.tsx diff --git a/packages/common/infra/src/framework/react/index.tsx b/packages/common/infra/src/framework/react/index.tsx index 8577bca001..4805e37ac4 100644 --- a/packages/common/infra/src/framework/react/index.tsx +++ b/packages/common/infra/src/framework/react/index.tsx @@ -71,7 +71,8 @@ export const FrameworkScope = ({ const nextStack = useMemo(() => { if (!scope) return provider; - return new FrameworkStackProvider([provider, scope.framework]); + // make sure the stack order is inside to outside + return new FrameworkStackProvider([scope.framework, provider]); }, [scope, provider]); return ( diff --git a/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx b/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx index 812aae7372..d8b57141e3 100644 --- a/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx +++ b/packages/frontend/component/src/ui/notification/desktop/notification-card.tsx @@ -1,3 +1,4 @@ +import { useI18n } from '@affine/i18n'; import { CloseIcon, InformationFillDuotoneIcon } from '@blocksuite/icons/rc'; import clsx from 'clsx'; import { useCallback } from 'react'; @@ -10,6 +11,7 @@ import * as styles from './styles.css'; export const DesktopNotificationCard = ({ notification, }: NotificationCardProps) => { + const t = useI18n(); const { theme = 'info', style = 'normal', @@ -17,6 +19,7 @@ export const DesktopNotificationCard = ({ iconColor, thumb, action, + error, title, footer, alignMessage = 'title', @@ -24,6 +27,12 @@ export const DesktopNotificationCard = ({ rootAttrs, } = notification; + const errorI18nKey = error ? (`error.${error.name}` as const) : undefined; + const errorTitle = + errorI18nKey && errorI18nKey in t + ? t[errorI18nKey](error?.data) + : undefined; + const onActionClicked = useCallback(() => { action?.onClick()?.catch(console.error); if (action?.autoClose !== false) { @@ -46,7 +55,7 @@ export const DesktopNotificationCard = ({ {icon} ) : null} -
{title}
+
{title ?? errorTitle}
{action ? (
diff --git a/packages/frontend/component/src/ui/notification/notify.tsx b/packages/frontend/component/src/ui/notification/notify.tsx index c53e6baa2a..b851926b19 100644 --- a/packages/frontend/component/src/ui/notification/notify.tsx +++ b/packages/frontend/component/src/ui/notification/notify.tsx @@ -1,3 +1,4 @@ +import { UserFriendlyError } from '@affine/error'; import { InformationFillDuotoneIcon, SingleSelectCheckSolidIcon, @@ -35,7 +36,16 @@ export function notify(notification: Notification, options?: ExternalToast) { }, options); } -notify.error = (notification: Notification, options?: ExternalToast) => { +notify.error = ( + notification: Notification | UserFriendlyError, + options?: ExternalToast +) => { + if (notification instanceof UserFriendlyError) { + notification = { + error: notification, + }; + } + return notify( { icon: , diff --git a/packages/frontend/component/src/ui/notification/types.ts b/packages/frontend/component/src/ui/notification/types.ts index 9344d0e8cb..eced811437 100644 --- a/packages/frontend/component/src/ui/notification/types.ts +++ b/packages/frontend/component/src/ui/notification/types.ts @@ -1,3 +1,4 @@ +import type { UserFriendlyError } from '@affine/error'; import type { HTMLAttributes, ReactNode } from 'react'; import type { ButtonProps } from '../button'; @@ -29,6 +30,7 @@ export interface Notification { thumb?: ReactNode; title?: ReactNode; message?: ReactNode; + error?: UserFriendlyError; icon?: ReactNode; iconColor?: string; footer?: ReactNode; diff --git a/packages/frontend/core/src/components/hooks/use-navigate-helper.ts b/packages/frontend/core/src/components/hooks/use-navigate-helper.ts index 94e1a0908e..dd0e2990b4 100644 --- a/packages/frontend/core/src/components/hooks/use-navigate-helper.ts +++ b/packages/frontend/core/src/components/hooks/use-navigate-helper.ts @@ -1,3 +1,4 @@ +import type { SettingTab } from '@affine/core/modules/dialogs/constant'; import { toURLSearchParams } from '@affine/core/modules/navigation'; import { getOpenUrlInDesktopAppLink } from '@affine/core/modules/open-in-app'; import type { DocMode } from '@blocksuite/affine/model'; @@ -183,6 +184,25 @@ export function useNavigateHelper() { [navigate] ); + const jumpToWorkspaceSettings = useCallback( + ( + workspaceId: string, + tab?: SettingTab, + logic: RouteLogic = RouteLogic.PUSH + ) => { + const searchParams = new URLSearchParams(); + if (tab) { + searchParams.set('tab', tab); + } + return navigate( + `/workspace/${workspaceId}/settings?${searchParams.toString()}`, + { + replace: logic === RouteLogic.REPLACE, + } + ); + }, + [navigate] + ); return useMemo( () => ({ jumpToPage, @@ -198,6 +218,7 @@ export function useNavigateHelper() { jumpToTag, jumpToOpenInApp, jumpToImportTemplate, + jumpToWorkspaceSettings, }), [ jumpToPage, @@ -213,6 +234,7 @@ export function useNavigateHelper() { jumpToTag, jumpToOpenInApp, jumpToImportTemplate, + jumpToWorkspaceSettings, ] ); } diff --git a/packages/frontend/core/src/components/notification/list.style.css.ts b/packages/frontend/core/src/components/notification/list.style.css.ts index 8f9280d581..72cce92945 100644 --- a/packages/frontend/core/src/components/notification/list.style.css.ts +++ b/packages/frontend/core/src/components/notification/list.style.css.ts @@ -34,6 +34,7 @@ export const itemContainer = style({ position: 'relative', padding: '8px', gap: '8px', + cursor: 'pointer', selectors: { [`&:hover:not([data-disabled="true"])`]: { backgroundColor: cssVarV2('layer/background/hoverOverlay'), @@ -81,9 +82,17 @@ export const itemDeleteButton = style({ export const itemNameLabel = style({ fontWeight: 'bold', color: cssVarV2('text/primary'), + display: 'inline-flex', + alignItems: 'center', + gap: '4px', + lineHeight: '22px', selectors: { [`&[data-inactived="true"]`]: { color: cssVarV2('text/placeholder'), }, }, }); + +export const itemActionButton = style({ + width: 'fit-content', +}); diff --git a/packages/frontend/core/src/components/notification/list.tsx b/packages/frontend/core/src/components/notification/list.tsx index 31657fbbb0..5b2157d780 100644 --- a/packages/frontend/core/src/components/notification/list.tsx +++ b/packages/frontend/core/src/components/notification/list.tsx @@ -1,16 +1,31 @@ -import { Avatar, IconButton, Scrollable, Skeleton } from '@affine/component'; +import { + Avatar, + Button, + IconButton, + notify, + Scrollable, + Skeleton, +} from '@affine/component'; +import { AcceptInviteService } from '@affine/core/modules/cloud'; import { type Notification, NotificationListService, NotificationType, } from '@affine/core/modules/notification'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { UserFriendlyError } from '@affine/error'; -import type { MentionNotificationBodyType } from '@affine/graphql'; +import type { + InvitationAcceptedNotificationBodyType, + InvitationBlockedNotificationBodyType, + InvitationNotificationBodyType, + MentionNotificationBodyType, +} from '@affine/graphql'; import { i18nTime, Trans, useI18n } from '@affine/i18n'; -import { DeleteIcon } from '@blocksuite/icons/rc'; +import { CollaborationIcon, DeleteIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useNavigateHelper } from '../hooks/use-navigate-helper'; import * as styles from './list.style.css'; export const NotificationList = () => { @@ -92,34 +107,24 @@ const NotificationItemSkeleton = () => { }; const NotificationItem = ({ notification }: { notification: Notification }) => { - const notificationListService = useService(NotificationListService); const t = useI18n(); const type = notification.type; - const handleDelete = useCallback(() => { - notificationListService.readNotification(notification.id).catch(err => { - console.error(err); - }); - }, [notificationListService, notification.id]); - - return ( + return type === NotificationType.Mention ? ( + + ) : type === NotificationType.InvitationAccepted ? ( + + ) : type === NotificationType.Invitation ? ( + + ) : type === NotificationType.InvitationBlocked ? ( + + ) : (
- {type === NotificationType.Mention ? ( - - ) : ( - <> - -
- {t['com.affine.notification.unsupported']()} ({type}) -
- - )} - } - onClick={handleDelete} - /> + +
+ {t['com.affine.notification.unsupported']()} ({type}) +
+
); }; @@ -129,13 +134,30 @@ const MentionNotificationItem = ({ }: { notification: Notification; }) => { + const notificationListService = useService(NotificationListService); + const { jumpToPageBlock } = useNavigateHelper(); const t = useI18n(); const body = notification.body as MentionNotificationBodyType; const memberInactived = !body.createdByUser; - const username = - body.createdByUser?.name ?? t['com.affine.inactive-member'](); + + const handleClick = useCallback(() => { + if (!body.workspace?.id) { + return; + } + notificationListService.readNotification(notification.id).catch(err => { + console.error(err); + }); + jumpToPageBlock( + body.workspace.id, + body.doc.id, + body.doc.mode, + body.doc.blockId ? [body.doc.blockId] : undefined, + body.doc.elementId ? [body.doc.elementId] : undefined + ); + }, [body, jumpToPageBlock, notificationListService, notification.id]); + return ( - <> +
, }} values={{ - username: username, - docTitle: body.doc.title ?? t['Untitled'](), + username: + body.createdByUser?.name ?? t['com.affine.inactive-member'](), + docTitle: body.doc.title || t['Untitled'](), }} /> @@ -166,6 +189,286 @@ const MentionNotificationItem = ({ })}
- + + + ); +}; + +const InvitationAcceptedNotificationItem = ({ + notification, +}: { + notification: Notification; +}) => { + const notificationListService = useService(NotificationListService); + const { jumpToWorkspaceSettings } = useNavigateHelper(); + const t = useI18n(); + const body = notification.body as InvitationAcceptedNotificationBodyType; + const memberInactived = !body.createdByUser; + + const handleClick = useCallback(() => { + notificationListService.readNotification(notification.id).catch(err => { + console.error(err); + }); + if (!body.workspace?.id) { + return; + } + jumpToWorkspaceSettings(body.workspace.id, 'workspace:members'); + }, [body, jumpToWorkspaceSettings, notification, notificationListService]); + + return ( +
+ +
+ + + ), + }} + values={{ + username: + body.createdByUser?.name ?? t['com.affine.inactive-member'](), + }} + /> + +
+ {i18nTime(notification.createdAt, { + relative: true, + })} +
+
+ +
+ ); +}; + +const InvitationBlockedNotificationItem = ({ + notification, +}: { + notification: Notification; +}) => { + const notificationListService = useService(NotificationListService); + const { jumpToWorkspaceSettings } = useNavigateHelper(); + const t = useI18n(); + const body = notification.body as InvitationBlockedNotificationBodyType; + const workspaceInactived = !body.workspace; + + const handleClick = useCallback(() => { + notificationListService.readNotification(notification.id).catch(err => { + console.error(err); + }); + if (!body.workspace?.id) { + return; + } + jumpToWorkspaceSettings(body.workspace.id, 'workspace:members'); + }, [body, jumpToWorkspaceSettings, notification, notificationListService]); + + return ( +
+ +
+ + + ), + }} + values={{ + workspaceName: + body.workspace?.name ?? t['com.affine.inactive-workspace'](), + }} + /> + +
+ {i18nTime(notification.createdAt, { + relative: true, + })} +
+
+ +
+ ); +}; + +const InvitationNotificationItem = ({ + notification, +}: { + notification: Notification; +}) => { + const t = useI18n(); + const body = notification.body as InvitationNotificationBodyType; + const memberInactived = !body.createdByUser; + const workspaceInactived = !body.workspace; + const workspacesService = useService(WorkspacesService); + const acceptInviteService = useService(AcceptInviteService); + const notificationListService = useService(NotificationListService); + const inviteId = body.inviteId; + const [isAccepting, setIsAccepting] = useState(false); + const { jumpToPage } = useNavigateHelper(); + + const WorkspaceNameWithIcon = useCallback( + ({ + children, + ...props + }: React.PropsWithChildren>) => { + return ( + + + {children} + + ); + }, + [] + ); + + const handleReadAndOpenWorkspace = useCallback(() => { + notificationListService.readNotification(notification.id).catch(err => { + console.error(err); + }); + if (!body.workspace?.id) { + return; // should never happen + } + jumpToPage(body.workspace.id, 'all'); + }, [body, jumpToPage, notification.id, notificationListService]); + + const handleAcceptInvite = useCallback(() => { + setIsAccepting(true); + acceptInviteService + .waitForAcceptInvite(inviteId) + .catch(err => { + const userFriendlyError = UserFriendlyError.fromAny(err); + if (userFriendlyError.is('ALREADY_IN_SPACE')) { + // ignore if the user is already in the workspace + return true; + } + throw err; + }) + .then(async value => { + if (value === false) { + // invite is expired + notify.error({ + title: t['com.affine.expired.page.title'](), + message: t['com.affine.expired.page.new-subtitle'](), + }); + notificationListService + .readNotification(notification.id) + .catch(err => { + console.error(err); + }); + return; + } else { + // invite is accepted + await workspacesService.list.waitForRevalidation(); + handleReadAndOpenWorkspace(); + } + }) + .catch(err => { + const userFriendlyError = UserFriendlyError.fromAny(err); + notify.error(userFriendlyError); + }) + .finally(() => { + setIsAccepting(false); + }); + }, [ + acceptInviteService, + handleReadAndOpenWorkspace, + inviteId, + notification, + notificationListService, + t, + workspacesService, + ]); + + return ( +
+ +
+ + + ), + 2: , + }} + values={{ + username: + body.createdByUser?.name ?? t['com.affine.inactive-member'](), + workspaceName: + body.workspace?.name ?? t['com.affine.inactive-workspace'](), + }} + /> + + {!workspaceInactived && ( + + )} +
+ {i18nTime(notification.createdAt, { + relative: true, + })} +
+
+ +
+ ); +}; + +const DeleteButton = ({ + notification, + onClick, +}: { + notification: Notification; + onClick?: () => void; +}) => { + const notificationListService = useService(NotificationListService); + + const handleDelete = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); // prevent trigger the click event of the parent element + + notificationListService.readNotification(notification.id).catch(err => { + console.error(err); + }); + onClick?.(); + }, + [notificationListService, notification.id, onClick] + ); + + return ( + } + onClick={handleDelete} + /> ); }; diff --git a/packages/frontend/core/src/desktop/dialogs/change-password/index.tsx b/packages/frontend/core/src/desktop/dialogs/change-password/index.tsx index d3b628ca26..3997a42a1b 100644 --- a/packages/frontend/core/src/desktop/dialogs/change-password/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/change-password/index.tsx @@ -21,7 +21,7 @@ import { } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService } from '@toeverything/infra'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; export const ChangePasswordDialog = ({ close, @@ -52,10 +52,12 @@ export const ChangePasswordDialog = ({ ); const serverName = useLiveData(server.config$.selector(c => c.serverName)); - if (!email) { - // should not happen - throw new Unreachable(); - } + useEffect(() => { + if (!account) { + // we are logged out, close the dialog + close(); + } + }, [account, close]); const onSendEmail = useAsyncCallback(async () => { setLoading(true); diff --git a/packages/frontend/core/src/desktop/pages/invite/index.tsx b/packages/frontend/core/src/desktop/pages/invite/index.tsx index 2204f4acfb..aa0aaba135 100644 --- a/packages/frontend/core/src/desktop/pages/invite/index.tsx +++ b/packages/frontend/core/src/desktop/pages/invite/index.tsx @@ -3,6 +3,8 @@ import { ExpiredPage, JoinFailedPage, } from '@affine/component/member-components'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { WorkspacesService } from '@affine/core/modules/workspace'; import { UserFriendlyError } from '@affine/error'; import { useLiveData, useService } from '@toeverything/infra'; import { useCallback, useEffect } from 'react'; @@ -17,6 +19,7 @@ import { AcceptInviteService, AuthService } from '../../../modules/cloud'; const AcceptInvite = ({ inviteId: targetInviteId }: { inviteId: string }) => { const { jumpToPage } = useNavigateHelper(); const acceptInviteService = useService(AcceptInviteService); + const workspacesService = useService(WorkspacesService); const error = useLiveData(acceptInviteService.error$); const inviteId = useLiveData(acceptInviteService.inviteId$); const inviteInfo = useLiveData(acceptInviteService.inviteInfo$); @@ -24,19 +27,20 @@ const AcceptInvite = ({ inviteId: targetInviteId }: { inviteId: string }) => { const loading = useLiveData(acceptInviteService.loading$); const navigateHelper = useNavigateHelper(); - const openWorkspace = useCallback(() => { + const openWorkspace = useAsyncCallback(async () => { if (!inviteInfo?.workspace.id) { return; } + await workspacesService.list.waitForRevalidation(); jumpToPage(inviteInfo.workspace.id, 'all', RouteLogic.REPLACE); - }, [jumpToPage, inviteInfo]); + }, [inviteInfo, workspacesService, jumpToPage]); const onOpenAffine = useCallback(() => { navigateHelper.jumpToIndex(); }, [navigateHelper]); useEffect(() => { - acceptInviteService.revalidate({ + acceptInviteService.acceptInvite({ inviteId: targetInviteId, }); }, [acceptInviteService, targetInviteId]); @@ -45,10 +49,10 @@ const AcceptInvite = ({ inviteId: targetInviteId }: { inviteId: string }) => { if (error && inviteId === targetInviteId) { const err = UserFriendlyError.fromAny(error); if (err.is('ALREADY_IN_SPACE')) { - return navigateHelper.jumpToIndex(); + return openWorkspace(); } } - }, [error, inviteId, navigateHelper, targetInviteId]); + }, [error, inviteId, navigateHelper, openWorkspace, targetInviteId]); if (loading || inviteId !== targetInviteId) { return null; diff --git a/packages/frontend/core/src/desktop/pages/workspace/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/index.tsx index a81fa48120..1522734a77 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/index.tsx @@ -123,17 +123,20 @@ export const Component = (): ReactElement => { // if workspace is not found, we should retry const retryTimesRef = useRef(3); useEffect(() => { - retryTimesRef.current = 3; // reset retry times - }, [params.workspaceId]); + if (params.workspaceId) { + retryTimesRef.current = 3; // reset retry times + workspacesService.list.revalidate(); + } + }, [params.workspaceId, workspacesService]); useEffect(() => { if (listLoading === false && meta === undefined) { - const timer = setInterval(() => { + const timer = setTimeout(() => { if (retryTimesRef.current > 0) { workspacesService.list.revalidate(); retryTimesRef.current--; } }, 5000); - return () => clearInterval(timer); + return () => clearTimeout(timer); } return; }, [listLoading, meta, workspaceNotFound, workspacesService]); diff --git a/packages/frontend/core/src/desktop/pages/workspace/settings/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/settings/index.tsx new file mode 100644 index 0000000000..3d04eedbe1 --- /dev/null +++ b/packages/frontend/core/src/desktop/pages/workspace/settings/index.tsx @@ -0,0 +1,29 @@ +import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; +import type { SettingTab } from '@affine/core/modules/dialogs/constant'; +import { WorkbenchService } from '@affine/core/modules/workbench'; +import { useService } from '@toeverything/infra'; +import { useEffect, useRef } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +export const Component = () => { + const workbenchService = useService(WorkbenchService); + const workspaceDialogService = useService(WorkspaceDialogService); + const workbench = workbenchService.workbench; + + const [searchParams] = useSearchParams(); + const tab = searchParams.get('tab') ?? undefined; + + const isOpened = useRef(false); + + useEffect(() => { + if (isOpened.current) { + return; + } + isOpened.current = true; // prevent open multiple times + workbench.openAll(); + workspaceDialogService.open('setting', { + activeTab: tab as SettingTab, + }); + }, [tab, workbench, workspaceDialogService]); + return null; +}; diff --git a/packages/frontend/core/src/desktop/workbench-router.ts b/packages/frontend/core/src/desktop/workbench-router.ts index 42e477f064..f4f962374f 100644 --- a/packages/frontend/core/src/desktop/workbench-router.ts +++ b/packages/frontend/core/src/desktop/workbench-router.ts @@ -37,6 +37,10 @@ export const workbenchRoutes = [ path: '/journals', lazy: () => import('./pages/journals'), }, + { + path: '/settings', + lazy: () => import('./pages/workspace/settings'), + }, { path: '*', lazy: () => import('./pages/404'), diff --git a/packages/frontend/core/src/modules/cloud/services/accept-invite.ts b/packages/frontend/core/src/modules/cloud/services/accept-invite.ts index 07b3ea9601..30e414b38e 100644 --- a/packages/frontend/core/src/modules/cloud/services/accept-invite.ts +++ b/packages/frontend/core/src/modules/cloud/services/accept-invite.ts @@ -9,7 +9,7 @@ import { Service, smartRetry, } from '@toeverything/infra'; -import { EMPTY, exhaustMap, mergeMap } from 'rxjs'; +import { EMPTY, mergeMap, switchMap } from 'rxjs'; import type { AcceptInviteStore } from '../stores/accept-invite'; import type { InviteInfoStore } from '../stores/invite-info'; @@ -29,8 +29,8 @@ export class AcceptInviteService extends Service { loading$ = new LiveData(false); error$ = new LiveData(null); - readonly revalidate = effect( - exhaustMap(({ inviteId }: { inviteId: string }) => { + readonly acceptInvite = effect( + switchMap(({ inviteId }: { inviteId: string }) => { if (!inviteId) { return EMPTY; } @@ -51,7 +51,9 @@ export class AcceptInviteService extends Service { this.accepted$.next(res); return EMPTY; }), - smartRetry(), + smartRetry({ + count: 1, + }), catchErrorInto(this.error$), onStart(() => { this.inviteId$.setValue(inviteId); @@ -66,7 +68,21 @@ export class AcceptInviteService extends Service { }) ); + async waitForAcceptInvite(inviteId: string) { + this.acceptInvite({ inviteId }); + await this.loading$.waitFor(f => !f); + if (this.accepted$.value) { + return true; // invite is accepted + } + + if (this.error$.value) { + throw this.error$.value; + } + + return false; // invite is expired + } + override dispose(): void { - this.revalidate.unsubscribe(); + this.acceptInvite.unsubscribe(); } } diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index 69e6db8ae2..9d9eaeeeef 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -14,7 +14,7 @@ "it-IT": 94, "it": 1, "ja": 94, - "ko": 60, + "ko": 59, "pl": 94, "pt-BR": 94, "ru": 94, diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index ce47cfdfc7..ce8e5e5900 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -1114,6 +1114,10 @@ export function useAFFiNEI18N(): { ["com.affine.auth.sign.auth.code.resend.hint"](options: { readonly second: string; }): string; + /** + * `Sent` + */ + ["com.affine.auth.sent"](): string; /** * `The verification link failed to be sent, please try again later.` */ @@ -7417,6 +7421,10 @@ export function useAFFiNEI18N(): { * `Transcribing` */ ["com.affine.attachmentViewer.audio.transcribing"](): string; + /** + * `Accept & Join` + */ + ["com.affine.notification.invitation.accept"](): string; /** * `An internal error occurred.` */ @@ -8451,4 +8459,30 @@ export const TypedTrans: { }, { a: JSX.Element; }>>; + /** + * `<1>{{username}} has accept your invitation` + */ + ["com.affine.notification.invitation-accepted"]: ComponentType>; + /** + * `There is an issue regarding your invitation to <1>{{workspaceName}} ` + */ + ["com.affine.notification.invitation-blocked"]: ComponentType>; + /** + * `<1>{{username}} invited you to join <2>{{workspaceName}}` + */ + ["com.affine.notification.invitation"]: ComponentType, { + ["1"]: JSX.Element; + ["2"]: JSX.Element; + }>>; } = /*#__PURE__*/ createProxy(createComponent); diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 2481c6524d..4c472dbcf4 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -270,6 +270,7 @@ "com.affine.auth.sign.auth.code.continue": "Continue with code", "com.affine.auth.sign.auth.code.resend": "Resend code", "com.affine.auth.sign.auth.code.resend.hint": "Resend in {{second}}s", + "com.affine.auth.sent": "Sent", "com.affine.auth.sent.change.email.fail": "The verification link failed to be sent, please try again later.", "com.affine.auth.sent.change.email.hint": "Verification link has been sent.", "com.affine.auth.sent.change.password.hint": "Reset password link has been sent.", @@ -1847,6 +1848,10 @@ "com.affine.integration.properties": "Integration properties", "com.affine.attachmentViewer.audio.notes": "Notes", "com.affine.attachmentViewer.audio.transcribing": "Transcribing", + "com.affine.notification.invitation-accepted": "<1>{{username}} has accept your invitation", + "com.affine.notification.invitation-blocked": "There is an issue regarding your invitation to <1>{{workspaceName}} ", + "com.affine.notification.invitation": "<1>{{username}} invited you to join <2>{{workspaceName}}", + "com.affine.notification.invitation.accept": "Accept & Join", "error.INTERNAL_SERVER_ERROR": "An internal error occurred.", "error.NETWORK_ERROR": "Network error.", "error.TOO_MANY_REQUEST": "Too many requests.",