actual/packages/desktop-client/src/components/Notifications.js
Tom French 9c0df36e16
Sort import in alphabetical order (#238)
* style: enforce sorting of imports

* style: alphabetize imports

* style: merge duplicated imports
2022-09-02 15:07:24 +01:00

229 lines
5.8 KiB
JavaScript

import React, { useState, useEffect, useMemo } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from 'loot-core/src/client/actions';
import {
View,
Text,
Button,
ButtonWithLoading,
Stack,
ExternalLink
} from 'loot-design/src/components/common';
import { styles, colors } from 'loot-design/src/style';
import Delete from 'loot-design/src/svg/Delete';
import Loading from 'loot-design/src/svg/v1/AnimatedLoading';
function compileMessage(message, actions, setLoading, onRemove) {
return (
<Stack spacing={2}>
{message.split(/\n\n/).map((paragraph, idx) => {
let parts = paragraph.split(/(\[[^\]]*\]\([^)]*\))/g);
return (
<Text key={idx} style={{ lineHeight: '1.4em' }}>
{parts.map((part, idx) => {
let match = part.match(/\[([^\]]*)\]\(([^)]*)\)/);
if (match) {
let [_, text, href] = match;
if (href[0] === '#') {
let actionName = href.slice(1);
return (
// eslint-disable-next-line
<a
href="#"
onClick={async e => {
e.preventDefault();
if (actions[actionName]) {
setLoading(true);
await actions[actionName]();
onRemove();
}
}}
>
{text}
</a>
);
}
return (
<ExternalLink key={idx} asAnchor={true} href={match[2]}>
{match[1]}
</ExternalLink>
);
}
return <Text key={idx}>{part}</Text>;
})}
</Text>
);
})}
</Stack>
);
}
function Notification({ notification, onRemove }) {
let {
id,
type,
title,
message,
messageActions,
sticky,
internal,
button
} = notification;
let [loading, setLoading] = useState(false);
let [overlayLoading, setOverlayLoading] = useState(false);
useEffect(() => {
if (type === 'error' && internal) {
console.error('Internal error:', internal);
}
if (!sticky) {
setTimeout(onRemove, 6500);
}
}, []);
let positive = type === 'message';
let error = type === 'error';
let processedMessage = useMemo(
() => compileMessage(message, messageActions, setOverlayLoading, onRemove),
[message, messageActions]
);
return (
<View
style={{
marginTop: 10,
color: positive ? colors.g3 : error ? colors.r3 : colors.y2
}}
>
<Stack
align="center"
direction="row"
style={{
padding: '14px 14px',
fontSize: 14,
backgroundColor: positive
? colors.g11
: error
? colors.r11
: colors.y10,
borderTop: `3px solid ${
positive ? colors.g5 : error ? colors.r5 : colors.y4
}`,
boxShadow: styles.shadowLarge,
maxWidth: 550,
'& a': { color: 'currentColor' }
}}
>
<Stack align="flex-start">
{title && (
<View style={{ fontWeight: 700, marginBottom: 10 }}>{title}</View>
)}
<View>{processedMessage}</View>
{button && (
<ButtonWithLoading
bare
loading={loading}
onClick={async () => {
setLoading(true);
await button.action();
onRemove();
setLoading(false);
}}
style={{
backgroundColor: 'transparent',
border: `1px solid ${
positive ? colors.g5 : error ? colors.r4 : colors.y3
}`,
color: 'currentColor',
fontSize: 14,
flexShrink: 0,
'&:hover, &:active': {
backgroundColor: positive
? colors.g9
: error
? colors.r10
: colors.y9
}
}}
>
{button.title}
</ButtonWithLoading>
)}
</Stack>
{sticky && (
<Button
bare
style={{ flexShrink: 0, color: 'currentColor' }}
onClick={onRemove}
>
<Delete style={{ width: 9, height: 9, color: 'currentColor' }} />
</Button>
)}
</Stack>
{overlayLoading && (
<View
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(250, 250, 250, .75)',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Loading
color="currentColor"
style={{ width: 20, height: 20, color: 'currentColor' }}
/>
</View>
)}
</View>
);
}
function Notifications({ notifications, removeNotification, style }) {
return (
<View
style={[
{
position: 'absolute',
bottom: 20,
right: 13,
zIndex: 10000
},
style
]}
>
{notifications.map(note => (
<Notification
key={note.id}
notification={note}
onRemove={() => {
if (note.onClose) {
note.onClose();
}
removeNotification(note.id);
}}
/>
))}
</View>
);
}
export default connect(
state => ({ notifications: state.notifications.notifications }),
dispatch => bindActionCreators(actions, dispatch)
)(Notifications);