feat(core): more notification type (#11209)

This commit is contained in:
EYHN
2025-03-27 02:10:49 +00:00
parent 8f124c5070
commit e311d3d1cb
4 changed files with 292 additions and 31 deletions

View File

@@ -18,6 +18,9 @@ import type {
InvitationAcceptedNotificationBodyType,
InvitationBlockedNotificationBodyType,
InvitationNotificationBodyType,
InvitationReviewApprovedNotificationBodyType,
InvitationReviewDeclinedNotificationBodyType,
InvitationReviewRequestNotificationBodyType,
MentionNotificationBodyType,
} from '@affine/graphql';
import { i18nTime, Trans, useI18n } from '@affine/i18n';
@@ -118,6 +121,12 @@ const NotificationItem = ({ notification }: { notification: Notification }) => {
<InvitationNotificationItem notification={notification} />
) : type === NotificationType.InvitationBlocked ? (
<InvitationBlockedNotificationItem notification={notification} />
) : type === NotificationType.InvitationReviewRequest ? (
<InvitationReviewRequestNotificationItem notification={notification} />
) : type === NotificationType.InvitationReviewDeclined ? (
<InvitationReviewDeclinedNotificationItem notification={notification} />
) : type === NotificationType.InvitationReviewApproved ? (
<InvitationReviewApprovedNotificationItem notification={notification} />
) : (
<div className={styles.itemContainer}>
<Avatar size={22} />
@@ -194,6 +203,189 @@ const MentionNotificationItem = ({
);
};
const InvitationReviewRequestNotificationItem = ({
notification,
}: {
notification: Notification;
}) => {
const notificationListService = useService(NotificationListService);
const { jumpToWorkspaceSettings } = useNavigateHelper();
const t = useI18n();
const body = notification.body as InvitationReviewRequestNotificationBodyType;
const memberInactived = !body.createdByUser;
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 (
<div className={styles.itemContainer} onClick={handleClick}>
<Avatar
size={22}
name={body.createdByUser?.name}
url={body.createdByUser?.avatarUrl}
/>
<div className={styles.itemMain}>
<span>
<Trans
i18nKey={'com.affine.notification.invitation-review-request'}
components={{
1: (
<b
className={styles.itemNameLabel}
data-inactived={memberInactived}
/>
),
2: <WorkspaceNameWithIcon data-inactived={workspaceInactived} />,
}}
values={{
username:
body.createdByUser?.name ?? t['com.affine.inactive-member'](),
workspaceName:
body.workspace?.name ?? t['com.affine.inactive-workspace'](),
}}
/>
</span>
<div className={styles.itemDate}>
{i18nTime(notification.createdAt, {
relative: true,
})}
</div>
</div>
<DeleteButton notification={notification} />
</div>
);
};
const InvitationReviewDeclinedNotificationItem = ({
notification,
}: {
notification: Notification;
}) => {
const t = useI18n();
const body =
notification.body as InvitationReviewDeclinedNotificationBodyType;
const memberInactived = !body.createdByUser;
const workspaceInactived = !body.workspace;
return (
<div className={styles.itemContainer}>
<Avatar
size={22}
name={body.createdByUser?.name}
url={body.createdByUser?.avatarUrl}
/>
<div className={styles.itemMain}>
<span>
<Trans
i18nKey={'com.affine.notification.invitation-review-declined'}
components={{
1: (
<b
className={styles.itemNameLabel}
data-inactived={memberInactived}
/>
),
2: <WorkspaceNameWithIcon data-inactived={workspaceInactived} />,
}}
values={{
username:
body.createdByUser?.name ?? t['com.affine.inactive-member'](),
workspaceName:
body.workspace?.name ?? t['com.affine.inactive-workspace'](),
}}
/>
</span>
<div className={styles.itemDate}>
{i18nTime(notification.createdAt, {
relative: true,
})}
</div>
</div>
<DeleteButton notification={notification} />
</div>
);
};
const InvitationReviewApprovedNotificationItem = ({
notification,
}: {
notification: Notification;
}) => {
const notificationListService = useService(NotificationListService);
const { jumpToPage } = useNavigateHelper();
const t = useI18n();
const body =
notification.body as InvitationReviewApprovedNotificationBodyType;
const memberInactived = !body.createdByUser;
const workspaceInactived = !body.workspace;
const handleClick = useCallback(() => {
notificationListService.readNotification(notification.id).catch(err => {
console.error(err);
});
if (!body.workspace?.id) {
return;
}
jumpToPage(body.workspace.id, 'all');
}, [body, jumpToPage, notification, notificationListService]);
return (
<div className={styles.itemContainer}>
<Avatar
size={22}
name={body.createdByUser?.name}
url={body.createdByUser?.avatarUrl}
/>
<div className={styles.itemMain}>
<span>
<Trans
i18nKey={'com.affine.notification.invitation-review-approved'}
components={{
1: (
<b
className={styles.itemNameLabel}
data-inactived={memberInactived}
/>
),
2: <WorkspaceNameWithIcon data-inactived={workspaceInactived} />,
}}
values={{
username:
body.createdByUser?.name ?? t['com.affine.inactive-member'](),
workspaceName:
body.workspace?.name ?? t['com.affine.inactive-workspace'](),
}}
/>
</span>
{!workspaceInactived && (
<Button
variant="secondary"
className={styles.itemActionButton}
onClick={handleClick}
>
{t[
'com.affine.notification.invitation-review-approved.open-workspace'
]()}
</Button>
)}
<div className={styles.itemDate}>
{i18nTime(notification.createdAt, {
relative: true,
})}
</div>
</div>
<DeleteButton notification={notification} />
</div>
);
};
const InvitationAcceptedNotificationItem = ({
notification,
}: {
@@ -320,21 +512,6 @@ const InvitationNotificationItem = ({
const [isAccepting, setIsAccepting] = useState(false);
const { jumpToPage } = useNavigateHelper();
const WorkspaceNameWithIcon = useCallback(
({
children,
...props
}: React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>) => {
return (
<b className={styles.itemNameLabel} {...props}>
<CollaborationIcon width={20} height={20} />
{children}
</b>
);
},
[]
);
const handleReadAndOpenWorkspace = useCallback(() => {
notificationListService.readNotification(notification.id).catch(err => {
console.error(err);
@@ -473,3 +650,15 @@ const DeleteButton = ({
/>
);
};
const WorkspaceNameWithIcon = ({
children,
...props
}: React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>) => {
return (
<b className={styles.itemNameLabel} {...props}>
<CollaborationIcon width={20} height={20} />
{children}
</b>
);
};

View File

@@ -1,26 +1,26 @@
{
"ar": 94,
"ar": 93,
"ca": 4,
"da": 4,
"de": 94,
"el-GR": 94,
"de": 93,
"el-GR": 93,
"en": 100,
"es-AR": 94,
"es-AR": 93,
"es-CL": 95,
"es": 94,
"fa": 94,
"fr": 94,
"es": 93,
"fa": 93,
"fr": 93,
"hi": 2,
"it-IT": 94,
"it-IT": 93,
"it": 1,
"ja": 94,
"ja": 93,
"ko": 59,
"pl": 94,
"pt-BR": 94,
"ru": 94,
"sv-SE": 94,
"uk": 94,
"pl": 93,
"pt-BR": 93,
"ru": 93,
"sv-SE": 93,
"uk": 93,
"ur": 2,
"zh-Hans": 94,
"zh-Hant": 94
"zh-Hans": 93,
"zh-Hant": 93
}

View File

@@ -7107,6 +7107,14 @@ export function useAFFiNEI18N(): {
* `No new notifications`
*/
["com.affine.notification.empty"](): string;
/**
* `Open workspace`
*/
["com.affine.notification.invitation-review-approved.open-workspace"](): string;
/**
* `Accept & Join`
*/
["com.affine.notification.invitation.accept"](): string;
/**
* `Tips`
*/
@@ -8474,6 +8482,62 @@ export const TypedTrans: {
["1"]: JSX.Element;
["2"]: JSX.Element;
}>>;
/**
* `<1>{{username}}</1> has accept your invitation`
*/
["com.affine.notification.invitation-accepted"]: ComponentType<TypedTransProps<{
readonly username: string;
}, {
["1"]: JSX.Element;
}>>;
/**
* `<1>{{username}}</1> has requested to join <2>{{workspaceName}}</2>`
*/
["com.affine.notification.invitation-review-request"]: ComponentType<TypedTransProps<Readonly<{
username: string;
workspaceName: string;
}>, {
["1"]: JSX.Element;
["2"]: JSX.Element;
}>>;
/**
* `<1>{{username}}</1> has declined your request to join <2>{{workspaceName}}</2>`
*/
["com.affine.notification.invitation-review-declined"]: ComponentType<TypedTransProps<Readonly<{
username: string;
workspaceName: string;
}>, {
["1"]: JSX.Element;
["2"]: JSX.Element;
}>>;
/**
* `<1>{{username}}</1> has approved your request to join <2>{{workspaceName}}</2>`
*/
["com.affine.notification.invitation-review-approved"]: ComponentType<TypedTransProps<Readonly<{
username: string;
workspaceName: string;
}>, {
["1"]: JSX.Element;
["2"]: JSX.Element;
}>>;
/**
* `There is an issue regarding your invitation to <1>{{workspaceName}}</1> `
*/
["com.affine.notification.invitation-blocked"]: ComponentType<TypedTransProps<{
readonly workspaceName: string;
}, {
["1"]: JSX.Element;
}>>;
/**
* `<1>{{username}}</1> invited you to join <2>{{workspaceName}}</2>`
*/
["com.affine.notification.invitation"]: ComponentType<TypedTransProps<Readonly<{
username: string;
workspaceName: string;
}>, {
["1"]: JSX.Element;
["2"]: JSX.Element;
}>>;
/**
* `Unable to join <1/> <2>{{workspaceName}}</2> due to insufficient seats available.`
*/

View File

@@ -1767,6 +1767,14 @@
"com.affine.notification.unsupported": "Unsupported message",
"com.affine.notification.mention": "<1>{{username}}</1> mentioned you in <2>{{docTitle}}</2>",
"com.affine.notification.empty": "No new notifications",
"com.affine.notification.invitation-accepted": "<1>{{username}}</1> has accept your invitation",
"com.affine.notification.invitation-review-request": "<1>{{username}}</1> has requested to join <2>{{workspaceName}}</2>",
"com.affine.notification.invitation-review-declined": "<1>{{username}}</1> has declined your request to join <2>{{workspaceName}}</2>",
"com.affine.notification.invitation-review-approved": "<1>{{username}}</1> has approved your request to join <2>{{workspaceName}}</2>",
"com.affine.notification.invitation-review-approved.open-workspace": "Open workspace",
"com.affine.notification.invitation-blocked": "There is an issue regarding your invitation to <1>{{workspaceName}}</1> ",
"com.affine.notification.invitation": "<1>{{username}}</1> invited you to join <2>{{workspaceName}}</2>",
"com.affine.notification.invitation.accept": "Accept & Join",
"tips": "Tips",
"Template": "Template",
"com.affine.template-list.delete": "Delete Template",