Skip to content
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bbccee4
chore: whitelist 'notification' path, add open notification functions…
AMIRKHANEF Oct 1, 2025
fa894c4
feat: introduce Notifications Settings page extension mode
AMIRKHANEF Oct 1, 2025
15cc8e9
chore: add notification settings path
AMIRKHANEF Oct 1, 2025
30e2a8d
feat: fetch referenda notifications, and resolve minor issues
AMIRKHANEF Oct 1, 2025
08013e3
Merge branch 'main' into notification
AMIRKHANEF Oct 4, 2025
a79d0ec
chore: add group function to utils
AMIRKHANEF Oct 4, 2025
5fdb9bf
chore: resolve styling problem in TwoToneText component
AMIRKHANEF Oct 5, 2025
e64e234
refactor: separate function, types and helpers
AMIRKHANEF Oct 5, 2025
81332b9
feat: create NotificationGroup.tsx
AMIRKHANEF Oct 5, 2025
9a879d8
chore: add notification items icons
AMIRKHANEF Oct 6, 2025
cd0afac
feat: show Notification on full-screen mode
AMIRKHANEF Oct 7, 2025
f1c21e4
fix: resolve fetching problem in payouts and minor style problems
AMIRKHANEF Oct 7, 2025
5d0dc90
refactor: refactor getting description information
AMIRKHANEF Oct 7, 2025
fbf4c95
Merge branch 'main' into notification
AMIRKHANEF Oct 8, 2025
f1b1f96
refactor: remove unused newReferendaNotification.js
AMIRKHANEF Oct 8, 2025
64de6a4
feat: add Notification Settings on full-screen mode
AMIRKHANEF Oct 11, 2025
b0e7a61
chore: adjust some styling issues
AMIRKHANEF Oct 11, 2025
22946e0
feat: display referenda notifications
AMIRKHANEF Oct 11, 2025
e6ac5bc
chore: display a message when the notification is off
AMIRKHANEF Oct 12, 2025
0e8eb4b
refactor: improve naming and notification settings initialization
AMIRKHANEF Oct 12, 2025
aacff8d
chore: display a message when it's the first time using notification …
AMIRKHANEF Oct 12, 2025
2e686f3
chore: refactor notification status, display loading state
AMIRKHANEF Oct 12, 2025
a15b71e
refactor: handle cold start
AMIRKHANEF Oct 12, 2025
9f31247
chore: display no notification message
AMIRKHANEF Oct 12, 2025
a97258e
refactor: handle saving fetched notification right away in a queue
AMIRKHANEF Oct 12, 2025
50cab8c
chore: adjust styling issues
AMIRKHANEF Oct 13, 2025
7cfeaa1
chore: remove chevron and add motion
AMIRKHANEF Oct 13, 2025
d8595f3
fix: resolve not synced notification setting changes, add 15 min offset
AMIRKHANEF Oct 13, 2025
6e5c4b5
fix: resolve saving notification problem
AMIRKHANEF Oct 14, 2025
a860f76
fix: resolve duplicate fetching information for migrated relay chains
AMIRKHANEF Oct 14, 2025
33f5369
fix: resolve new notification alert, sort items in the day
AMIRKHANEF Oct 14, 2025
8ecacfc
Merge branch 'main' into notification
AMIRKHANEF Oct 21, 2025
68e8f81
chore: resolve conflict
AMIRKHANEF Oct 21, 2025
a800e53
chore: fix minor issues
AMIRKHANEF Oct 21, 2025
998015c
chore: resolve minor issues in notification settings
AMIRKHANEF Oct 21, 2025
ffd09ef
chore: resolve minor issues in notification utils functions
AMIRKHANEF Oct 21, 2025
37150f1
chore: add a new constant for the fetching notifications items thresh…
AMIRKHANEF Oct 21, 2025
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
3 changes: 2 additions & 1 deletion packages/extension-base/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const START_WITH_PATH = [
'/send/',
'/settingsfs/',
'/stake/',
'/socialRecovery/'
'/socialRecovery/',
'/notification/'
] as const;

const ROOT_PATH = [
Expand Down
88 changes: 69 additions & 19 deletions packages/extension-polkagate/src/components/ActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,43 @@ import { noop } from '@polkadot/util';
import { useIsDark, useIsExtensionPopup, useIsHovered } from '../hooks';
import TwoToneText from './TwoToneText';

interface IconElementProp {
iconVariant?: 'Bulk' | 'Broken' | 'TwoTone' | 'Outline' | 'Linear' | 'Bold';
iconVariantOnHover?: 'Bulk' | 'Broken' | 'TwoTone' | 'Outline' | 'Linear' | 'Bold';
IconName: Icon | undefined;
disabled?: boolean;
isBlueish?: boolean;
iconSize?: number;
iconAlwaysBold?: boolean;
hovered: boolean;
}

const IconElement = ({ IconName, disabled, hovered, iconAlwaysBold, iconSize, iconVariant, iconVariantOnHover, isBlueish }: IconElementProp) => {
const theme = useTheme();

return (IconName
? (
<IconName
color={
disabled
? '#BEAAD84D'
: isBlueish
? theme.palette.text.highlight
: theme.palette.primary.main
}
size={iconSize}
variant={
(iconAlwaysBold ?? hovered)
? iconVariantOnHover ?? 'Bold'
: iconVariant ?? 'Bulk'
}
/>)
: undefined);
};

export interface ActionButtonProps {
StartIcon?: Icon;
EndIcon?: Icon;
iconVariant?: 'Bulk' | 'Broken' | 'TwoTone' | 'Outline' | 'Linear' | 'Bold';
iconVariantOnHover?: 'Bulk' | 'Broken' | 'TwoTone' | 'Outline' | 'Linear' | 'Bold';
contentPlacement?: 'start' | 'center' | 'end';
Expand All @@ -27,7 +62,7 @@ export interface ActionButtonProps {
variant?: 'text' | 'contained' | 'outlined';
}

export default function ActionButton ({ StartIcon, contentPlacement = 'start', disabled, iconAlwaysBold, iconSize = 20, iconVariant, iconVariantOnHover, isBlueish, isBusy, onClick, style, text, variant }: ActionButtonProps): React.ReactElement<ActionButtonProps> {
export default function ActionButton ({ EndIcon, StartIcon, contentPlacement = 'start', disabled, iconAlwaysBold, iconSize = 20, iconVariant, iconVariantOnHover, isBlueish, isBusy, onClick, style, text, variant }: ActionButtonProps): React.ReactElement<ActionButtonProps> {
const theme = useTheme();
const isDark = useIsDark();
const containerRef = useRef(null);
Expand Down Expand Up @@ -66,6 +101,16 @@ export default function ActionButton ({ StartIcon, contentPlacement = 'start', d
}
};

const EndIconStyle = {
'& .MuiButton-endIcon': {
marginLeft: '10px',
marginRight: 0
},
'& .MuiButton-endIcon svg': {
color: disabled ? '#BEAAD84D' : '#BEAAD8'
}
};

const renderText = useMemo(() => {
if (typeof text === 'string') {
return <span style={{ color: disabled ? '#BEAAD84D' : isDark ? isBlueish ? '#809ACB' : '#BEAAD8' : '#745D8B', whiteSpace: 'nowrap', ...ButtonFontStyle }}>
Expand All @@ -88,32 +133,37 @@ export default function ActionButton ({ StartIcon, contentPlacement = 'start', d
return (
<Button
disabled={disabled || isBusy}
endIcon={
<IconElement
IconName={EndIcon}
disabled={disabled}
hovered={hovered}
iconAlwaysBold={iconAlwaysBold}
iconSize={iconSize}
iconVariant={iconVariant}
iconVariantOnHover={iconVariantOnHover}
isBlueish={isBlueish}
/>}
onClick={onClick ?? noop}
ref={containerRef}
startIcon={StartIcon
? (
<StartIcon
color={
disabled
? '#BEAAD84D'
: isBlueish
? theme.palette.text.highlight
: theme.palette.primary.main
}
size={iconSize}
variant={
(iconAlwaysBold ?? hovered)
? iconVariantOnHover ?? 'Bold'
: iconVariant ?? 'Bulk'
}
/>)
: undefined}
startIcon={
<IconElement
IconName={StartIcon}
disabled={disabled}
hovered={hovered}
iconAlwaysBold={iconAlwaysBold}
iconSize={iconSize}
iconVariant={iconVariant}
iconVariantOnHover={iconVariantOnHover}
isBlueish={isBlueish}
/>}
sx={{
'&.Mui-disabled': {
backgroundColor: '#2D1E4A4D'
},
...GeneralButtonStyle,
...StartIconStyle,
...EndIconStyle,
...style
}}
variant={variant}
Expand Down
11 changes: 8 additions & 3 deletions packages/extension-polkagate/src/components/ActionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ interface Props {
onClick: () => void;
style?: SxProps<Theme>;
title: string;
children?: React.ReactNode;
showColorBall?: boolean;
showChevron?: boolean;
}

function ActionCard ({ Icon, description, iconColor = '#AA83DC', iconSize = 30, iconWithBackground, iconWithoutTransform, logoIcon, onClick, style, title }: Props): React.ReactElement {
function ActionCard ({ Icon, children, description, iconColor = '#AA83DC', iconSize = 30, iconWithBackground, iconWithoutTransform, logoIcon, onClick, showChevron = true, showColorBall = true, style, title }: Props): React.ReactElement {
const theme = useTheme();
const isDark = useIsDark();
const containerRef = useRef(null);
Expand All @@ -44,6 +47,7 @@ function ActionCard ({ Icon, description, iconColor = '#AA83DC', iconSize = 30,
const colorBallStyle: React.CSSProperties = {
backgroundColor: '#CC429D',
borderRadius: '50%',
display: showColorBall ? 'initial' : 'none',
filter: 'blur(28px)', // Glow effect
height: '42px',
left: '1px',
Expand Down Expand Up @@ -87,17 +91,18 @@ function ActionCard ({ Icon, description, iconColor = '#AA83DC', iconSize = 30,
sx={{ ...IconStyle, height: '34px', p: '2px', width: '34px' }}
/>
}
<Grid container item>
<Grid container item xs>
<Grid alignItems='center' container item>
<Typography color={hovered ? '#AA83DC' : theme.palette.text.primary} sx={{ transition: 'all 250ms ease-out' }} variant='B-2'>
{title}
</Typography>
<ArrowRight2 color={hovered ? '#AA83DC' : theme.palette.text.primary} size='12' style={chevronStyle} />
{showChevron && <ArrowRight2 color={hovered ? '#AA83DC' : theme.palette.text.primary} size='12' style={chevronStyle} />}
</Grid>
<Typography color={theme.palette.text.secondary} textAlign='left' variant='B-4'>
{description}
</Typography>
</Grid>
{children}
<div style={colorBallStyle} />
</Container>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Props {

function TwoToneText ({ backgroundColor, color = '#BEAAD8', style = {}, text, textPartInColor = '' }: Props): React.ReactElement {
if (!textPartInColor) {
return <span>{text}</span>;
return <span style={{ ...style }}>{text}</span>;
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,94 @@
// Copyright 2019-2025 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* eslint-disable react/jsx-first-prop-new-line */

import { Box, Grid } from '@mui/material';
import { Notification } from 'iconsax-react';
import React, { useCallback } from 'react';
import { Notification as NotificationIcon } from 'iconsax-react';
import React, { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';

import { useIsExtensionPopup, useNotifications } from '@polkadot/extension-polkagate/src/hooks';
import { ExtensionPopups } from '@polkadot/extension-polkagate/src/util/constants';
import { useExtensionPopups } from '@polkadot/extension-polkagate/src/util/handleExtensionPopup';

import Notification from '../../notification';

import { useAlerts, useTranslation } from '@polkadot/extension-polkagate/src/hooks';
const NotificationButton = ({ hasNewNotification, onClick }: { hasNewNotification: boolean; onClick: () => void; }) => (
<Grid alignItems='center' container item justifyContent='center' onClick={onClick}
sx={{
'&:hover': { background: '#674394' },
'&:hover .notification-dot': { borderColor: '#674394' },
backdropFilter: 'blur(20px)',
background: '#2D1E4A80',
borderRadius: '12px',
boxShadow: '0px 0px 24px 8px #4E2B7259 inset',
cursor: 'pointer',
height: '32px',
position: 'relative',
transition: 'all 250ms ease-out',
width: '32px'
}}
>
<Box className='notification-dot'
sx={{
bgcolor: '#FF4FB9',
border: '1.5px solid #2D1E4A',
borderRadius: '50%',
display: hasNewNotification ? 'block' : 'none',
height: '9px',
position: 'absolute',
right: '5px',
top: '5px',
transition: 'border-color 200ms ease',
width: '9px',
zIndex: 1
}}
/>
<NotificationIcon
color='#AA83DC'
size='20'
style={{ cursor: 'pointer', transform: 'rotate(30deg)' }}
variant='Bold'
/>
</Grid>
);

function Notifications (): React.ReactElement {
const { notify } = useAlerts();
const { t } = useTranslation();
const isExtension = useIsExtensionPopup();
const navigate = useNavigate();
const { extensionPopup, extensionPopupCloser, extensionPopupOpener } = useExtensionPopups();
const { notificationItems } = useNotifications();

const onClick = useCallback(() => {
notify(t('Coming Soon!'), 'info');
}, [notify, t]);
const hasNewNotification = useMemo(() => {
if (!notificationItems) {
return false;
}

return (
<Grid
alignItems='center'
container
item
justifyContent='center'
onClick={onClick}
sx={{
'&:hover': {
background: '#674394'
},
'&:hover .notification-dot': {
borderColor: '#674394'
},
return Object.values(notificationItems).flat().some(({ read }) => !read);
}, [notificationItems]);

backdropFilter: 'blur(20px)',
background: '#2D1E4A80',
borderRadius: '12px',
boxShadow: '0px 0px 24px 8px #4E2B7259 inset',
cursor: 'pointer',
height: '32px',
position: 'relative',
transition: 'all 250ms ease-out',
width: '32px'
const onClick = useCallback(() => {
if (isExtension) {
navigate('/notification') as void;

}}
>
<Box
className='notification-dot'
sx={{
bgcolor: '#FF4FB9',
border: '1.5px solid #2D1E4A',
borderRadius: '50%',
height: '9px',
position: 'absolute',
right: '5px',
top: '5px',
transition: 'border-color 200ms ease',
width: '9px',
zIndex: 1
}}
/>
<Notification
color='#AA83DC'
size='20'
style={{ cursor: 'pointer', transform: 'rotate(30deg)' }}
variant='Bold'
return;
}

extensionPopupOpener(ExtensionPopups.NOTIFICATION)();
}, [extensionPopupOpener, isExtension, navigate]);

return (
<>
<NotificationButton
hasNewNotification={hasNewNotification}
onClick={onClick}
/>
</Grid>
{extensionPopup === ExtensionPopups.NOTIFICATION &&
<Notification
handleClose={extensionPopupCloser}
/>}
</>
);
}

Expand Down
Loading
Loading