mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
refactor(core): move actions to footer of notification card (#11894)
This PR move all actions button to the footer of `NotificationCard`. There are some example as following: ### No Changes    ### Changes ### Before  #### After 
This commit is contained in:
@@ -4,7 +4,8 @@ import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { Button, IconButton } from '../../button';
|
||||
import type { NotificationCardProps } from '../types';
|
||||
import { FlexWrapper } from '../../layout/wrapper';
|
||||
import type { NotificationActionProps, NotificationCardProps } from '../types';
|
||||
import { getCardVars } from '../utils';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
@@ -18,10 +19,9 @@ export const DesktopNotificationCard = ({
|
||||
icon = <InformationFillDuotoneIcon />,
|
||||
iconColor,
|
||||
thumb,
|
||||
action,
|
||||
actions,
|
||||
error,
|
||||
title,
|
||||
footer,
|
||||
alignMessage = 'title',
|
||||
onDismiss,
|
||||
rootAttrs,
|
||||
@@ -33,13 +33,6 @@ export const DesktopNotificationCard = ({
|
||||
? t[errorI18nKey](error?.data)
|
||||
: undefined;
|
||||
|
||||
const onActionClicked = useCallback(() => {
|
||||
action?.onClick()?.catch(console.error);
|
||||
if (action?.autoClose !== false) {
|
||||
onDismiss?.();
|
||||
}
|
||||
}, [action, onDismiss]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={getCardVars(style, theme, iconColor)}
|
||||
@@ -56,18 +49,6 @@ export const DesktopNotificationCard = ({
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.title}>{title || errorTitle}</div>
|
||||
|
||||
{action ? (
|
||||
<div className={clsx(styles.headAlignWrapper, styles.action)}>
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
onClick={onActionClicked}
|
||||
{...action.buttonProps}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
data-float={!!thumb}
|
||||
className={clsx(styles.headAlignWrapper, styles.closeButton)}
|
||||
@@ -83,8 +64,42 @@ export const DesktopNotificationCard = ({
|
||||
<main data-align={alignMessage} className={styles.main}>
|
||||
{notification.message}
|
||||
</main>
|
||||
<footer>{footer}</footer>
|
||||
<footer>
|
||||
<FlexWrapper marginTop={8} justifyContent="flex-end" gap="12px">
|
||||
{actions?.map(action => (
|
||||
<NotificationCardAction
|
||||
key={action.key}
|
||||
action={action}
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
))}
|
||||
</FlexWrapper>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationCardAction = ({
|
||||
action,
|
||||
onDismiss,
|
||||
}: NotificationActionProps) => {
|
||||
const onActionClicked = useCallback(() => {
|
||||
action.onClick()?.catch(console.error);
|
||||
if (action.autoClose !== false) {
|
||||
onDismiss?.();
|
||||
}
|
||||
}, [action, onDismiss]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="plain"
|
||||
data-testid={action.key}
|
||||
className={styles.actionButton}
|
||||
onClick={onActionClicked}
|
||||
{...action.buttonProps}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -55,14 +55,14 @@ export const title = style({
|
||||
fontSize: 15,
|
||||
marginRight: 10,
|
||||
});
|
||||
export const action = style({
|
||||
marginRight: 16,
|
||||
});
|
||||
|
||||
export const actionButton = style({
|
||||
color: actionTextColor,
|
||||
position: 'relative',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
fontSize: cssVar('fontSm'),
|
||||
lineHeight: '22px',
|
||||
});
|
||||
export const closeButton = style({
|
||||
selectors: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, IconButton } from '../../button';
|
||||
import { Modal } from '../../modal';
|
||||
import type { NotificationCardProps } from '../types';
|
||||
import type { NotificationActionProps, NotificationCardProps } from '../types';
|
||||
import { getCardVars } from '../utils';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
@@ -70,8 +70,7 @@ const MobileNotifyDetail = ({
|
||||
iconColor,
|
||||
title,
|
||||
message,
|
||||
footer,
|
||||
action,
|
||||
actions,
|
||||
error,
|
||||
} = notification;
|
||||
const t = useI18n();
|
||||
@@ -87,12 +86,6 @@ const MobileNotifyDetail = ({
|
||||
},
|
||||
[onClose]
|
||||
);
|
||||
const onActionClicked = useCallback(() => {
|
||||
action?.onClick()?.catch(console.error);
|
||||
if (action?.autoClose !== false) {
|
||||
onClose?.();
|
||||
}
|
||||
}, [action, onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -114,14 +107,33 @@ const MobileNotifyDetail = ({
|
||||
<main className={styles.detailContent}>{message}</main>
|
||||
{/* actions */}
|
||||
<div className={styles.detailActions}>
|
||||
{action ? (
|
||||
<Button onClick={onActionClicked} {...action.buttonProps}>
|
||||
{action.label}
|
||||
</Button>
|
||||
) : null}
|
||||
{footer}
|
||||
{actions?.map(action => (
|
||||
<NotificationCardAction
|
||||
key={action.key}
|
||||
action={action}
|
||||
onDismiss={onClose}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationCardAction = ({
|
||||
action,
|
||||
onDismiss,
|
||||
}: NotificationActionProps) => {
|
||||
const onActionClicked = useCallback(() => {
|
||||
action.onClick()?.catch(console.error);
|
||||
if (action.autoClose !== false) {
|
||||
onDismiss?.();
|
||||
}
|
||||
}, [action, onDismiss]);
|
||||
|
||||
return (
|
||||
<Button onClick={onActionClicked} {...action.buttonProps}>
|
||||
{action.label}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -181,10 +181,13 @@ export const WithAction: StoryFn = () => {
|
||||
),
|
||||
style,
|
||||
theme,
|
||||
action: {
|
||||
label: 'UNDO',
|
||||
onClick: () => console.log('undo'),
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
key: 'undo',
|
||||
label: 'UNDO',
|
||||
onClick: () => console.log('undo'),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -204,11 +207,14 @@ export const WithAction: StoryFn = () => {
|
||||
{
|
||||
title: 'Disable auto close',
|
||||
message: 'Test with disable auto close',
|
||||
action: {
|
||||
label: 'UNDO',
|
||||
onClick: () => console.log('undo'),
|
||||
autoClose: false,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
key: 'undo',
|
||||
label: 'UNDO',
|
||||
onClick: () => console.log('undo'),
|
||||
autoClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ duration: 22222222 }
|
||||
);
|
||||
@@ -296,25 +302,12 @@ export const DifferentSize: StoryFn = () => {
|
||||
{ duration: 60000 }
|
||||
);
|
||||
};
|
||||
const openWithFooter = () => {
|
||||
notify(
|
||||
{
|
||||
title: 'With footer',
|
||||
message: 'With basic title and one line message',
|
||||
footer: (
|
||||
<Button onClick={() => console.log('clicked')}>Click me</Button>
|
||||
),
|
||||
},
|
||||
{ duration: 60000 }
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Root style={{ display: 'flex', gap: 8 }}>
|
||||
<Button onClick={openTiny}>Open Tiny</Button>
|
||||
<Button onClick={openNormal}>Open Normal</Button>
|
||||
<Button onClick={openLarge}>Open Large</Button>
|
||||
<Button onClick={openWithThumb}>Open with thumb</Button>
|
||||
<Button onClick={openWithFooter}>Open with footer</Button>
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@ export interface Notification {
|
||||
background?: string;
|
||||
foreground?: string;
|
||||
alignMessage?: 'title' | 'icon';
|
||||
action?: {
|
||||
actions?: {
|
||||
key: string;
|
||||
label: ReactNode;
|
||||
onClick: (() => void) | (() => Promise<void>);
|
||||
buttonProps?: ButtonProps;
|
||||
@@ -22,7 +23,7 @@ export interface Notification {
|
||||
* @default true
|
||||
*/
|
||||
autoClose?: boolean;
|
||||
};
|
||||
}[];
|
||||
|
||||
rootAttrs?: HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
@@ -33,7 +34,6 @@ export interface Notification {
|
||||
error?: UserFriendlyError;
|
||||
icon?: ReactNode;
|
||||
iconColor?: string;
|
||||
footer?: ReactNode;
|
||||
|
||||
// events
|
||||
onDismiss?: () => void;
|
||||
@@ -50,3 +50,8 @@ export interface NotificationCustomRendererProps {
|
||||
export interface NotificationCardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
notification: Notification;
|
||||
}
|
||||
|
||||
export interface NotificationActionProps {
|
||||
action: NonNullable<Notification['actions']>[number];
|
||||
onDismiss: Notification['onDismiss'];
|
||||
}
|
||||
|
||||
@@ -109,12 +109,15 @@ export function copyAsImage(std: BlockStdScope) {
|
||||
notify.error({
|
||||
title: I18n.t('com.affine.copy.asImage.notAvailable.title'),
|
||||
message: I18n.t('com.affine.copy.asImage.notAvailable.message'),
|
||||
action: {
|
||||
label: I18n.t('com.affine.copy.asImage.notAvailable.action'),
|
||||
onClick: () => {
|
||||
window.open('https://affine.pro/download');
|
||||
actions: [
|
||||
{
|
||||
key: 'download',
|
||||
label: I18n.t('com.affine.copy.asImage.notAvailable.action'),
|
||||
onClick: () => {
|
||||
window.open('https://affine.pro/download');
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Input,
|
||||
type Notification,
|
||||
notify,
|
||||
toast,
|
||||
type ToastOptions,
|
||||
@@ -96,17 +97,25 @@ export function patchNotificationService({
|
||||
throw new Error('Invalid notification accent');
|
||||
}
|
||||
|
||||
const toAffineNotificationActions = (
|
||||
actions: (typeof notification)['actions']
|
||||
): Notification['actions'] => {
|
||||
if (!actions) return undefined;
|
||||
|
||||
return actions.map(({ label, onClick, key }) => {
|
||||
return {
|
||||
key,
|
||||
label: toReactNode(label),
|
||||
onClick,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const toastId = fn(
|
||||
{
|
||||
title: toReactNode(notification.title),
|
||||
message: toReactNode(notification.message),
|
||||
footer: toReactNode(notification.footer),
|
||||
action: notification.action?.onClick
|
||||
? {
|
||||
label: toReactNode(notification.action?.label),
|
||||
onClick: notification.action.onClick,
|
||||
}
|
||||
: undefined,
|
||||
actions: toAffineNotificationActions(notification.actions),
|
||||
onDismiss: notification.onClose,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,10 +20,6 @@ export const thumbContent = style({
|
||||
height: 'calc(100% + 4px)',
|
||||
});
|
||||
|
||||
export const actionButton = style({
|
||||
fontSize: cssVar('fontSm'),
|
||||
lineHeight: '22px',
|
||||
});
|
||||
export const getStartedButtonText = style({
|
||||
color: cssVar('textSecondaryColor'),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, FlexWrapper, notify } from '@affine/component';
|
||||
import { notify } from '@affine/component';
|
||||
import { type Notification } from '@affine/component/ui/notification';
|
||||
import { SubscriptionService } from '@affine/core/modules/cloud';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
@@ -63,6 +64,38 @@ export const AIOnboardingEdgeless = () => {
|
||||
});
|
||||
}, [workspaceDialogService]);
|
||||
|
||||
const actions = useMemo(() => {
|
||||
const result: NonNullable<Notification['actions']> = [
|
||||
{
|
||||
key: 'get-started',
|
||||
label: (
|
||||
<span className={styles.getStartedButtonText}>
|
||||
{t['com.affine.ai-onboarding.edgeless.get-started']()}
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!aiSubscription) {
|
||||
result.push({
|
||||
key: 'purchase',
|
||||
label: (
|
||||
<span className={styles.purchaseButtonText}>
|
||||
{t['com.affine.ai-onboarding.edgeless.purchase']()}
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
goToPricingPlans();
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}, [aiSubscription, goToPricingPlans, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (generalAIOnboardingOpened) return;
|
||||
if (notifyId) return;
|
||||
@@ -83,50 +116,13 @@ export const AIOnboardingEdgeless = () => {
|
||||
thumb: <EdgelessOnboardingAnimation />,
|
||||
alignMessage: 'icon',
|
||||
onDismiss: () => toggleEdgelessAIOnboarding(false),
|
||||
footer: (
|
||||
<FlexWrapper marginTop={8} justifyContent="flex-end" gap="12px">
|
||||
<Button
|
||||
onClick={() => {
|
||||
notify.dismiss(id);
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
}}
|
||||
variant="plain"
|
||||
className={styles.actionButton}
|
||||
>
|
||||
<span className={styles.getStartedButtonText}>
|
||||
{t['com.affine.ai-onboarding.edgeless.get-started']()}
|
||||
</span>
|
||||
</Button>
|
||||
{aiSubscription ? null : (
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
variant="plain"
|
||||
onClick={() => {
|
||||
goToPricingPlans();
|
||||
notify.dismiss(id);
|
||||
toggleEdgelessAIOnboarding(false);
|
||||
}}
|
||||
>
|
||||
<span className={styles.purchaseButtonText}>
|
||||
{t['com.affine.ai-onboarding.edgeless.purchase']()}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
),
|
||||
actions,
|
||||
},
|
||||
{ duration: 1000 * 60 * 10 }
|
||||
);
|
||||
edgelessNotifyId$.next(id);
|
||||
}, 1000);
|
||||
}, [
|
||||
aiSubscription,
|
||||
generalAIOnboardingOpened,
|
||||
goToPricingPlans,
|
||||
mode,
|
||||
notifyId,
|
||||
t,
|
||||
]);
|
||||
}, [actions, generalAIOnboardingOpened, mode, notifyId, t]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, notify } from '@affine/component';
|
||||
import { type Notification, notify } from '@affine/component';
|
||||
import {
|
||||
RouteLogic,
|
||||
useNavigateHelper,
|
||||
@@ -8,7 +8,7 @@ import { useI18n } from '@affine/i18n';
|
||||
import { AiIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { toggleLocalAIOnboarding } from './apis';
|
||||
import * as styles from './local.dialog.css';
|
||||
@@ -29,51 +29,41 @@ const LocalOnboardingAnimation = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
|
||||
const t = useI18n();
|
||||
const authService = useService(AuthService);
|
||||
const loginStatus = useLiveData(authService.session.status$);
|
||||
const loggedIn = loginStatus === 'authenticated';
|
||||
const { jumpToSignIn } = useNavigateHelper();
|
||||
|
||||
return (
|
||||
<div className={styles.footerActions}>
|
||||
<a href="https://ai.affine.pro" target="_blank" rel="noreferrer">
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
variant="plain"
|
||||
onClick={onDismiss}
|
||||
>
|
||||
{t['com.affine.ai-onboarding.local.action-learn-more']()}
|
||||
</Button>
|
||||
</a>
|
||||
{loggedIn ? null : (
|
||||
<Button
|
||||
className={styles.actionButton}
|
||||
variant="plain"
|
||||
onClick={() => {
|
||||
onDismiss();
|
||||
jumpToSignIn('', RouteLogic.REPLACE, {}, { initCloud: 'true' });
|
||||
}}
|
||||
>
|
||||
{t['com.affine.ai-onboarding.local.action-get-started']()}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AIOnboardingLocal = () => {
|
||||
const t = useI18n();
|
||||
const authService = useService(AuthService);
|
||||
const notifyId = useLiveData(localNotifyId$);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const { jumpToSignIn } = useNavigateHelper();
|
||||
|
||||
const loginStatus = useLiveData(authService.session.status$);
|
||||
const notSignedIn = loginStatus !== 'authenticated';
|
||||
|
||||
const actions = useMemo(() => {
|
||||
const result: NonNullable<Notification['actions']> = [
|
||||
{
|
||||
key: 'learn-more',
|
||||
label: t['com.affine.ai-onboarding.local.action-learn-more'](),
|
||||
onClick: () => {
|
||||
window.open('https://ai.affine.pro', '_blank', 'noreferrer');
|
||||
},
|
||||
},
|
||||
];
|
||||
if (notSignedIn) {
|
||||
result.push({
|
||||
key: 'get-started',
|
||||
label: t['com.affine.ai-onboarding.local.action-get-started'](),
|
||||
onClick: () => {
|
||||
jumpToSignIn('', RouteLogic.REPLACE, {}, { initCloud: 'true' });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [t, jumpToSignIn, notSignedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!notSignedIn) return;
|
||||
// if (!notSignedIn) return;
|
||||
if (notifyId) return;
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
@@ -95,21 +85,14 @@ export const AIOnboardingLocal = () => {
|
||||
thumb: <LocalOnboardingAnimation />,
|
||||
alignMessage: 'icon',
|
||||
onDismiss: () => toggleLocalAIOnboarding(false),
|
||||
footer: (
|
||||
<FooterActions
|
||||
onDismiss={() => {
|
||||
toggleLocalAIOnboarding(false);
|
||||
notify.dismiss(id);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
actions,
|
||||
rootAttrs: { className: styles.card },
|
||||
},
|
||||
{ duration: 1000 * 60 * 10 }
|
||||
);
|
||||
localNotifyId$.next(id);
|
||||
}, 1000);
|
||||
}, [notSignedIn, notifyId, t]);
|
||||
}, [actions, notSignedIn, notifyId, t]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -6,13 +6,6 @@ export const notifyHeader = style({
|
||||
fontSize: 15,
|
||||
});
|
||||
|
||||
export const notifyFooter = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
gap: 12,
|
||||
paddingTop: 8,
|
||||
});
|
||||
|
||||
export const actionButton = style({
|
||||
fontSize: cssVar('fontSm'),
|
||||
fontWeight: 500,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, notify } from '@affine/component';
|
||||
import { type Notification, notify } from '@affine/component';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useRef } from 'react';
|
||||
@@ -7,48 +7,9 @@ import {
|
||||
actionButton,
|
||||
cancelButton,
|
||||
confirmButton,
|
||||
notifyFooter,
|
||||
notifyHeader,
|
||||
} from './notify.css';
|
||||
|
||||
interface SubscriptionChangedNotifyFooterProps {
|
||||
onCancel: () => void;
|
||||
onConfirm?: () => void;
|
||||
to: string;
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
}
|
||||
|
||||
const SubscriptionChangedNotifyFooter = ({
|
||||
to,
|
||||
okText,
|
||||
cancelText,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: SubscriptionChangedNotifyFooterProps) => {
|
||||
return (
|
||||
<div className={notifyFooter}>
|
||||
<Button
|
||||
className={clsx(actionButton, cancelButton)}
|
||||
size={'default'}
|
||||
onClick={onCancel}
|
||||
variant="plain"
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<a href={to} target="_blank" rel="noreferrer">
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
className={clsx(actionButton, confirmButton)}
|
||||
variant="plain"
|
||||
>
|
||||
{okText}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const useDowngradeNotify = () => {
|
||||
const t = useI18n();
|
||||
const prevNotifyIdRef = useRef<string | number | null>(null);
|
||||
@@ -56,6 +17,30 @@ export const useDowngradeNotify = () => {
|
||||
return useCallback(
|
||||
(link: string) => {
|
||||
prevNotifyIdRef.current && notify.dismiss(prevNotifyIdRef.current);
|
||||
|
||||
const actions: Notification['actions'] = [
|
||||
{
|
||||
key: 'later',
|
||||
label: t['com.affine.payment.downgraded-notify.later'](),
|
||||
onClick: () => {},
|
||||
buttonProps: {
|
||||
className: clsx(actionButton, cancelButton),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'ok',
|
||||
label: BUILD_CONFIG.isElectron
|
||||
? t['com.affine.payment.downgraded-notify.ok-client']()
|
||||
: t['com.affine.payment.downgraded-notify.ok-web'](),
|
||||
onClick: () => {
|
||||
window.open(link, '_blank', 'noreferrer');
|
||||
},
|
||||
buttonProps: {
|
||||
className: clsx(actionButton, confirmButton),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const id = notify(
|
||||
{
|
||||
title: (
|
||||
@@ -66,19 +51,7 @@ export const useDowngradeNotify = () => {
|
||||
message: t['com.affine.payment.downgraded-notify.content'](),
|
||||
alignMessage: 'title',
|
||||
icon: null,
|
||||
footer: (
|
||||
<SubscriptionChangedNotifyFooter
|
||||
to={link}
|
||||
okText={
|
||||
BUILD_CONFIG.isElectron
|
||||
? t['com.affine.payment.downgraded-notify.ok-client']()
|
||||
: t['com.affine.payment.downgraded-notify.ok-web']()
|
||||
}
|
||||
cancelText={t['com.affine.payment.downgraded-notify.later']()}
|
||||
onCancel={() => notify.dismiss(id)}
|
||||
onConfirm={() => notify.dismiss(id)}
|
||||
/>
|
||||
),
|
||||
actions,
|
||||
},
|
||||
{ duration: 24 * 60 * 60 * 1000 }
|
||||
);
|
||||
|
||||
@@ -43,10 +43,13 @@ export const OverCapacityNotification = () => {
|
||||
title: t['com.affine.payment.storage-limit.new-title'](),
|
||||
message:
|
||||
t['com.affine.payment.storage-limit.new-description.owner'](),
|
||||
action: {
|
||||
label: t['com.affine.payment.upgrade'](),
|
||||
onClick: jumpToPricePlan,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
key: 'upgrade',
|
||||
label: t['com.affine.payment.upgrade'](),
|
||||
onClick: jumpToPricePlan,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
notify.warning({
|
||||
|
||||
@@ -87,14 +87,17 @@ const BackupWorkspaceItem = ({ item }: { item: BackupWorkspaceItem }) => {
|
||||
}
|
||||
notify.success({
|
||||
title: t['com.affine.settings.workspace.backup.import.success'](),
|
||||
action: {
|
||||
label:
|
||||
t['com.affine.settings.workspace.backup.import.success.action'](),
|
||||
onClick: () => {
|
||||
jumpToPage(workspaceId, 'all');
|
||||
actions: [
|
||||
{
|
||||
key: 'open',
|
||||
label:
|
||||
t['com.affine.settings.workspace.backup.import.success.action'](),
|
||||
onClick: () => {
|
||||
jumpToPage(workspaceId, 'all');
|
||||
},
|
||||
autoClose: false,
|
||||
},
|
||||
autoClose: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
setMenuOpen(false);
|
||||
setImporting(false);
|
||||
|
||||
@@ -459,45 +459,49 @@ export class AtMenuConfigService extends Service {
|
||||
]({
|
||||
username,
|
||||
}),
|
||||
action: {
|
||||
label: 'Invite',
|
||||
onClick: async () => {
|
||||
track.$.sharePanel.$.inviteUserDocRole({
|
||||
control: 'member list',
|
||||
role: 'reader',
|
||||
});
|
||||
|
||||
try {
|
||||
await this.docGrantedUsersService.updateUserRole(
|
||||
id,
|
||||
DocRole.Reader
|
||||
);
|
||||
|
||||
await notificationService.mentionUser(
|
||||
id,
|
||||
workspaceId,
|
||||
{
|
||||
id: docId,
|
||||
title:
|
||||
this.docDisplayMetaService.title$(docId).value,
|
||||
blockId: block.blockId,
|
||||
mode: mode as GraphqlDocMode,
|
||||
}
|
||||
);
|
||||
|
||||
notify.success({
|
||||
title: I18n.t(
|
||||
'com.affine.editor.at-menu.invited-and-notified'
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
key: 'invite',
|
||||
label: 'Invite',
|
||||
onClick: async () => {
|
||||
track.$.sharePanel.$.inviteUserDocRole({
|
||||
control: 'member list',
|
||||
role: 'reader',
|
||||
});
|
||||
} catch (error) {
|
||||
const err = UserFriendlyError.fromAny(error);
|
||||
notify.error({
|
||||
title: I18n[`error.${err.name}`](err.data),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await this.docGrantedUsersService.updateUserRole(
|
||||
id,
|
||||
DocRole.Reader
|
||||
);
|
||||
|
||||
await notificationService.mentionUser(
|
||||
id,
|
||||
workspaceId,
|
||||
{
|
||||
id: docId,
|
||||
title:
|
||||
this.docDisplayMetaService.title$(docId)
|
||||
.value,
|
||||
blockId: block.blockId,
|
||||
mode: mode as GraphqlDocMode,
|
||||
}
|
||||
);
|
||||
|
||||
notify.success({
|
||||
title: I18n.t(
|
||||
'com.affine.editor.at-menu.invited-and-notified'
|
||||
),
|
||||
});
|
||||
} catch (error) {
|
||||
const err = UserFriendlyError.fromAny(error);
|
||||
notify.error({
|
||||
title: I18n[`error.${err.name}`](err.data),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
notify.error({
|
||||
|
||||
Reference in New Issue
Block a user