Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: JSON Notifications #420

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
12b7b76
feat: added in Notifications to redux store
TacosTonight Jan 2, 2022
7219f0c
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 2, 2022
6e233c7
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 8, 2022
72780aa
feat: changed state for notifications and moved message filtering to …
TacosTonight Jan 8, 2022
adf1fee
draft issue: having trouble with updating the json file and getting t…
TacosTonight Jan 9, 2022
96bffe7
feat: added NotificationBanner and Banner components
TacosTonight Jan 9, 2022
263fee7
feat: added styling and transitions for notifications
TacosTonight Jan 10, 2022
40cc10e
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 10, 2022
ac6e8f0
chore: added await to getNotificationMessages
TacosTonight Jan 10, 2022
294d571
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 10, 2022
85d939c
chore: addressed the non styling comments in the PR and made appropri…
TacosTonight Jan 15, 2022
1a83e39
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 15, 2022
a21d4df
chore: changed unicode symbols to icons
TacosTonight Jan 16, 2022
2edb63e
chore: updated z-index for Banner and spacing for NotificationBanner
TacosTonight Jan 16, 2022
2a231a4
chore: updated styling in Banner
TacosTonight Jan 16, 2022
c56dcf3
feat: moved styling from Banner to NotificationBanner and updated tra…
TacosTonight Jan 18, 2022
ad3756c
fix: fix react transition group showing occupying same space
FiboApe Jan 19, 2022
8ca39e8
Merge pull request #1 from TacosTonight/notificationbanner-transition…
TacosTonight Jan 19, 2022
4420084
chore: deleted example notification messages
TacosTonight Jan 20, 2022
552a581
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Jan 20, 2022
47df76f
chore: updated imports to new linting standard
TacosTonight Jan 20, 2022
b096afa
feat: addressed comments in PR and moved styling from NotificationBan…
TacosTonight Feb 11, 2022
117c1e3
Merge remote-tracking branch 'upstream/develop' into notificationbanner
TacosTonight Feb 11, 2022
6cec903
Merge branch 'develop' into notificationbanner
turtlemoji Apr 18, 2022
3502a88
Merge branch 'develop' of github.com:yearn/yearn-finance-v3 into noti…
turtlemoji Apr 23, 2022
b4d5e6c
fix: bad selector type
turtlemoji Apr 23, 2022
a025b33
style: fixes position color and others
turtlemoji Apr 23, 2022
dd158bb
fix: animation
turtlemoji Apr 23, 2022
8deea87
Merge branch 'develop' into notificationbanner
turtlemoji May 5, 2022
46a8bfb
Merge branch 'develop' into notificationbanner
turtlemoji May 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/client/components/common/Banner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FC } from 'react';
import styled from 'styled-components';

interface BannerProps {
children: React.ReactNode;
}

export const Banner: FC<BannerProps> = ({ children }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can probably remove this file since it does nothing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component should be the presentational component. I suggest moving all the styling from NotificationBanner to this one. Banner should receive props like children and onClose.

return <>{children}</>;
};
1 change: 1 addition & 0 deletions src/client/components/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './Banner';
export * from './Box';
export * from './Button';
export * from './Card';
Expand Down
43 changes: 23 additions & 20 deletions src/client/containers/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@store';
import { useAppTranslation, useAppDispatch, useAppSelector, useWindowDimensions, usePrevious } from '@hooks';
import { Navigation, Navbar, Footer } from '@components/app';
import { Modals, Alerts } from '@containers';
import { Modals, Alerts, NotificationBanner } from '@containers';
import { getConfig } from '@config';
import { Network, Route } from '@types';

Expand Down Expand Up @@ -145,24 +145,27 @@ export const Layout: FC = ({ children }) => {
}

return (
<StyledLayout>
<Alerts />
<Modals />
<Navigation />

<Content collapsedSidebar={collapsedSidebar} useTabbar={isMobile}>
<Navbar
title={t(`navigation.${path}`)}
walletAddress={selectedAddress}
addressEnsName={addressEnsName}
onWalletClick={() => dispatch(WalletActions.walletSelect({ network: currentNetwork }))}
selectedNetwork={currentNetwork}
networkOptions={SUPPORTED_NETWORKS}
onNetworkChange={(network) => dispatch(NetworkActions.changeNetwork({ network: network as Network }))}
/>
{children}
<Footer />
</Content>
</StyledLayout>
<>
<NotificationBanner />
<StyledLayout>
<Alerts />
<Modals />
<Navigation />

<Content collapsedSidebar={collapsedSidebar} useTabbar={isMobile}>
<Navbar
title={t(`navigation.${path}`)}
walletAddress={selectedAddress}
addressEnsName={addressEnsName}
onWalletClick={() => dispatch(WalletActions.walletSelect({ network: currentNetwork }))}
selectedNetwork={currentNetwork}
networkOptions={SUPPORTED_NETWORKS}
onNetworkChange={(network) => dispatch(NetworkActions.changeNetwork({ network: network as Network }))}
/>
{children}
<Footer />
</Content>
</StyledLayout>
</>
);
};
121 changes: 121 additions & 0 deletions src/client/containers/NotificationBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { FC } from 'react';
import { first } from 'lodash';
import styled from 'styled-components';
import { TransitionGroup, CSSTransition } from 'react-transition-group';

import { useAppSelector, useAppDispatch } from '@hooks';
import { NotificationsActions, NotificationsSelectors } from '@store';
import { Banner, Markdown, Icon, CloseIcon, InfoIcon, WarningFilledIcon, WarningIcon } from '@components/common';

const StyledCloseIcon = styled(Icon)`
width: 1.5rem;
height: 1rem;
margin-top: 2.5px;
cursor: pointer;
&:hover {
opacity: 0.7;
fill: ${({ theme }) => theme.colors.primaryVariant};
}
`;

const StyledSymbol = styled(Icon)`
width: 2.5rem;
height: 1.6rem;
margin-right: -2.5px;
`;

const StyledMarkdown = styled(Markdown)`
a {
text-decoration: underline;
}
`;

const StyledNotification = styled.div`
display: flex;
align-items: center;
> * {
padding-right: 5px;
}
`;

const StyledNotificationBanner = styled.div`
background: ${({ theme }) => theme.colors.surface};
height: rem;
display: flex;
align-items: center;
justify-content: center;
position: relative;
`;

const StyledTransitionGroup = styled(TransitionGroup)`
.transition-appear {
opacity: 0;
}
.transition-appear-active {
opacity: 1;
transition: opacity 800ms;
}
.transition-enter {
opacity: 0;
}
.transition-enter-active {
opacity: 1;
transition: opacity 800ms;
}
.transition-exit {
opacity: 1;
}
.transition-exit-active {
opacity: 0;
position: absolute;
transition: opacity 800ms;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
}
`;

export const NotificationBanner: FC = () => {
const latestMessage = first(useAppSelector(NotificationsSelectors.selectActiveMessages));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we need a container component instead of just having this all on the banner component?

const dispatch = useAppDispatch();
if (!latestMessage) {
return null;
}

let messageSymbol: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string | undefined }>;
switch (latestMessage.type) {
case 'info':
messageSymbol = InfoIcon;
break;
case 'warning':
messageSymbol = WarningFilledIcon;
break;
case 'critical':
messageSymbol = WarningIcon;
break;
default:
messageSymbol = InfoIcon;
}

return (
<StyledNotificationBanner>
<StyledTransitionGroup>
<CSSTransition appear={true} key={latestMessage.id} timeout={800} classNames={'transition'}>
<Banner>
<StyledNotification>
<StyledSymbol Component={messageSymbol} />
<StyledMarkdown>{latestMessage.message}</StyledMarkdown>
<StyledCloseIcon
Component={CloseIcon}
onClick={() => dispatch(NotificationsActions.dismissMessage(latestMessage.id))}
/>
</StyledNotification>
</Banner>
</CSSTransition>
</StyledTransitionGroup>
</StyledNotificationBanner>
);
};
1 change: 1 addition & 0 deletions src/client/containers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './Layout';
export * from './Alerts';
export * from './Modals';
export * from './Themable';
export * from './NotificationBanner';
1 change: 1 addition & 0 deletions src/client/data/notificationMessages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
xgambitox marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 8 additions & 2 deletions src/core/frameworks/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { createLogger } from 'redux-logger';
import { save, load, clear } from 'redux-localstorage-simple';
import { merge, cloneDeep, get } from 'lodash';

import rootReducer, { themeInitialState, walletInitialState, settingsInitialState } from '@store/modules';
import rootReducer, {
themeInitialState,
walletInitialState,
settingsInitialState,
notificationsInitialState,
} from '@store/modules';
import { enableDevTools } from '@utils';
import { DIContainer } from '@types';

Expand All @@ -12,10 +17,11 @@ export const getStore = (extraArgument?: any) => {
theme: cloneDeep(themeInitialState),
wallet: cloneDeep(walletInitialState),
settings: cloneDeep(settingsInitialState),
notifications: cloneDeep(notificationsInitialState),
};
const persistConfig = {
namespace: 'yearn',
states: ['theme', 'wallet.name', 'settings', 'network'],
states: ['theme', 'wallet.name', 'settings', 'network', 'notifications.dismissedMessageIds'],
xgambitox marked this conversation as resolved.
Show resolved Hide resolved
};
const logger = createLogger({ collapsed: true });
const middlewareOptions = {
Expand Down
4 changes: 4 additions & 0 deletions src/core/store/modules/app/app.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TokensActions } from '../tokens/tokens.actions';
import { VaultsActions } from '../vaults/vaults.actions';
import { LabsActions } from '../labs/labs.actions';
import { IronBankActions } from '../ironBank/ironBank.actions';
import { NotificationsActions } from '../notifications/notifications.actions';

/* -------------------------------------------------------------------------- */
/* Clear State */
Expand Down Expand Up @@ -44,6 +45,9 @@ const initApp = createAsyncThunk<void, void, ThunkAPI>('app/initApp', async (_ar
} else if (wallet.name) {
await dispatch(WalletActions.walletSelect({ walletName: wallet.name, network: network.current }));
}

await dispatch(NotificationsActions.getNotificationMessages());

// TODO use when sdk ready
// dispatch(initSubscriptions());
});
Expand Down
8 changes: 8 additions & 0 deletions src/core/store/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ import { SettingsSelectors } from './settings/settings.selectors';
import userReducer, { userInitialState } from './user/user.reducer';
import { UserActions } from './user/user.actions';
import { UserSelectors } from './user/user.selectors';
// Notification State
import notificationsReducer, { notificationsInitialState } from './notifications/notifications.reducer';
import { NotificationsActions } from './notifications/notifications.actions';
import { NotificationsSelectors } from './notifications/notifications.selectors';

const rootReducer: Reducer<RootState> = combineReducers({
app: appReducer,
Expand All @@ -68,6 +72,7 @@ const rootReducer: Reducer<RootState> = combineReducers({
labs: labsReducer,
settings: settingsReducer,
user: userReducer,
notifications: notificationsReducer,
});

export default rootReducer;
Expand All @@ -87,6 +92,7 @@ export {
LabsActions,
SettingsActions,
UserActions,
NotificationsActions,
};

// Selectors
Expand All @@ -103,6 +109,7 @@ export {
SettingsSelectors,
LabsSelectors,
UserSelectors,
NotificationsSelectors,
};

// initialState
Expand All @@ -120,4 +127,5 @@ export {
labsInitialState,
settingsInitialState,
userInitialState,
notificationsInitialState,
};
28 changes: 28 additions & 0 deletions src/core/store/modules/notifications/notifications.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';

import { ThunkAPI } from '@frameworks/redux';
import { Message } from '@types';
import notificationMessages from '@client/data/notificationMessages.json';

const dismissMessage = createAction<number>('notifications/dismissMessage');

/* -------------------------------------------------------------------------- */
/* Fetch Data */
/* -------------------------------------------------------------------------- */
const importedNotifications = notificationMessages as Message[];

const getNotificationMessages = createAsyncThunk<Message[], void, ThunkAPI>(
'notifications/getNotificationMessages',
async () => {
return importedNotifications;
xgambitox marked this conversation as resolved.
Show resolved Hide resolved
}
);

/* -------------------------------------------------------------------------- */
/* Exports */
/* -------------------------------------------------------------------------- */

export const NotificationsActions = {
getNotificationMessages,
dismissMessage,
};
42 changes: 42 additions & 0 deletions src/core/store/modules/notifications/notifications.reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createReducer } from '@reduxjs/toolkit';

import { initialStatus, NotificationsState } from '@types';

import { NotificationsActions } from './notifications.actions';

export const notificationsInitialState: NotificationsState = {
messages: [],
dismissedMessageIds: [],
statusMap: {
getNotificationMessages: { ...initialStatus },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just do getNotificationMessages: initialStatus

},
};

const { getNotificationMessages, dismissMessage } = NotificationsActions;

const notificationsReducer = createReducer(notificationsInitialState, (builder) => {
builder
.addCase(dismissMessage, (state, actions) => {
state.dismissedMessageIds = [...state.dismissedMessageIds, actions.payload];
})

/* -------------------------------------------------------------------------- */
/* Fetch Data */
/* -------------------------------------------------------------------------- */
.addCase(getNotificationMessages.pending, (state) => {
state.statusMap.getNotificationMessages = { loading: true };
})

.addCase(getNotificationMessages.rejected, (state, { error }) => {
state.statusMap.getNotificationMessages = { loading: false, error: error.message };
})

.addCase(getNotificationMessages.fulfilled, (state, { payload: messages }) => {
state.messages = messages.filter((message) => {
return message.active === true;
});
state.statusMap.getNotificationMessages = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be initialStatus now right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does make sense to set it as initialStatus, since after the request is fulfilled, loading should be false and there shouldn't be an error.

I noticed in other reducers however that the .fulfilled is set to {}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer being more explicit and having initialStatus too, but as you have it also works now since we dont have expressions like if(getNotificationMessages.loading === false) .... But we need to be careful of that.

});
});

export default notificationsReducer;
23 changes: 23 additions & 0 deletions src/core/store/modules/notifications/notifications.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createSelector } from '@reduxjs/toolkit';

import { RootState, Message } from '@types';

/* ---------------------------------- State --------------------------------- */
const selectDismissedMessageIds = (state: RootState) => state.notifications.dismissedMessageIds;
const selectMessages = (state: RootState) => state.notifications.messages;
const selectActiveMessages = createSelector<RootState, Message[], Number[], Message[]>(
[selectMessages, selectDismissedMessageIds],
(messages, dismissedMessageIds) => {
const activeMessages = messages.filter((message) => {
TacosTonight marked this conversation as resolved.
Show resolved Hide resolved
return !dismissedMessageIds.includes(message.id);
});
return activeMessages;
}
);

/* --------------------------------- Exports -------------------------------- */
export const NotificationsSelectors = {
selectDismissedMessageIds,
selectMessages,
selectActiveMessages,
};
8 changes: 8 additions & 0 deletions src/core/types/Notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type Classification = 'info' | 'warning' | 'critical';

export interface Message {
id: number;
type: Classification;
active: boolean;
message: string;
}
Loading