Skip to content

Commit fb3cff1

Browse files
committed
Replace red notification indicators with liquid blue themed badges featuring gradient, glow effects and animations
1 parent 751511d commit fb3cff1

9 files changed

Lines changed: 702 additions & 7 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import styled, { keyframes } from 'styled-components';
2+
import { Badge } from 'antd';
3+
4+
const pulse = keyframes`
5+
0% {
6+
box-shadow: 0 0 0 0 rgba(0, 255, 255, 0.7);
7+
transform: scale(1);
8+
}
9+
50% {
10+
box-shadow: 0 0 0 4px rgba(0, 255, 255, 0.3), 0 0 20px rgba(0, 255, 255, 0.5);
11+
transform: scale(1.05);
12+
}
13+
100% {
14+
box-shadow: 0 0 0 0 rgba(0, 255, 255, 0.7);
15+
transform: scale(1);
16+
}
17+
`;
18+
19+
const glow = keyframes`
20+
0% {
21+
opacity: 0.8;
22+
}
23+
50% {
24+
opacity: 1;
25+
}
26+
100% {
27+
opacity: 0.8;
28+
}
29+
`;
30+
31+
export const StyledLiquidBadge = styled(Badge)`
32+
& .ant-badge-count {
33+
background: linear-gradient(135deg, #00ffff 0%, #06b6d4 100%);
34+
border: none;
35+
box-shadow:
36+
0 0 12px rgba(0, 255, 255, 0.6),
37+
0 0 24px rgba(0, 255, 255, 0.4),
38+
inset 0 0 8px rgba(255, 255, 255, 0.3);
39+
animation: ${pulse} 2s ease-in-out infinite;
40+
font-weight: 600;
41+
color: rgba(0, 0, 0, 0.9);
42+
backdrop-filter: blur(4px);
43+
-webkit-backdrop-filter: blur(4px);
44+
}
45+
46+
& .ant-badge-dot {
47+
background: radial-gradient(circle, #ffffff 0%, #00ffff 40%, #06b6d4 100%);
48+
width: 10px;
49+
height: 10px;
50+
box-shadow:
51+
0 0 8px rgba(0, 255, 255, 0.8),
52+
0 0 16px rgba(0, 255, 255, 0.6),
53+
0 0 24px rgba(0, 255, 255, 0.4);
54+
animation: ${pulse} 2s ease-in-out infinite;
55+
border: 1px solid rgba(255, 255, 255, 0.3);
56+
}
57+
58+
& .ant-badge-count-sm {
59+
background: linear-gradient(135deg, #00ffff 0%, #06b6d4 100%);
60+
min-width: 18px;
61+
height: 18px;
62+
line-height: 18px;
63+
font-size: 11px;
64+
padding: 0 5px;
65+
box-shadow:
66+
0 0 8px rgba(0, 255, 255, 0.5),
67+
0 0 16px rgba(0, 255, 255, 0.3);
68+
animation: ${glow} 2s ease-in-out infinite;
69+
color: rgba(0, 0, 0, 0.85);
70+
font-weight: 600;
71+
}
72+
73+
&.notification-badge {
74+
& .ant-badge-count {
75+
right: -3px;
76+
top: -3px;
77+
}
78+
}
79+
`;
80+
81+
// Tab badge with more subtle animation for inline use
82+
export const TabBadge = styled(Badge)`
83+
& .ant-badge-count {
84+
background: linear-gradient(135deg, #00ffff 0%, #14b8a6 100%);
85+
border: none;
86+
box-shadow:
87+
0 0 6px rgba(0, 255, 255, 0.5),
88+
inset 0 0 4px rgba(255, 255, 255, 0.2);
89+
color: rgba(0, 0, 0, 0.85);
90+
font-weight: 600;
91+
animation: ${glow} 3s ease-in-out infinite;
92+
}
93+
94+
& .ant-badge-count-sm {
95+
background: linear-gradient(135deg, #00ffff 0%, #14b8a6 100%);
96+
min-width: 16px;
97+
height: 16px;
98+
line-height: 16px;
99+
font-size: 10px;
100+
padding: 0 4px;
101+
box-shadow:
102+
0 0 6px rgba(0, 255, 255, 0.4);
103+
color: rgba(0, 0, 0, 0.8);
104+
font-weight: 600;
105+
animation: ${glow} 3s ease-in-out infinite;
106+
}
107+
`;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { BadgeProps } from 'antd';
3+
import * as S from './LiquidBlueBadge.styles';
4+
5+
export interface LiquidBlueBadgeProps extends BadgeProps {
6+
variant?: 'default' | 'tab';
7+
}
8+
9+
export const LiquidBlueBadge: React.FC<LiquidBlueBadgeProps> = ({
10+
variant = 'default',
11+
className,
12+
...props
13+
}) => {
14+
const BadgeComponent = variant === 'tab' ? S.TabBadge : S.StyledLiquidBadge;
15+
16+
return (
17+
<BadgeComponent
18+
className={`${className || ''} ${variant === 'default' ? 'notification-badge' : ''}`}
19+
{...props}
20+
/>
21+
);
22+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './LiquidBlueBadge';

src/components/header/components/notificationsDropdown/NotificationsDropdown.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect, useCallback } from 'react';
22
import { BellOutlined } from '@ant-design/icons';
33
import { BaseButton } from '@app/components/common/BaseButton/BaseButton';
4-
import { BaseBadge } from '@app/components/common/BaseBadge/BaseBadge';
4+
import { LiquidBlueBadge } from '@app/components/common/LiquidBlueBadge/LiquidBlueBadge';
55
import { PaymentNotificationsOverlay } from '@app/components/header/components/notificationsDropdown/PaymentNotificationsOverlay';
66
import ReportNotificationsOverlay from '@app/components/header/components/notificationsDropdown/ReportNotificationsOverlay';
77
import { usePaymentNotifications } from '@app/hooks/usePaymentNotifications';
@@ -87,7 +87,12 @@ export const NotificationsDropdown: React.FC = () => {
8787
<span>
8888
{paymentsLabel}
8989
{unreadPaymentCount > 0 && (
90-
<BaseBadge count={unreadPaymentCount} size="small" style={{ marginLeft: '5px' }} />
90+
<LiquidBlueBadge
91+
count={unreadPaymentCount}
92+
size="small"
93+
variant="tab"
94+
style={{ marginLeft: '5px' }}
95+
/>
9196
)}
9297
</span>
9398
),
@@ -111,7 +116,12 @@ export const NotificationsDropdown: React.FC = () => {
111116
<span>
112117
{reportsLabel}
113118
{unreadReportCount > 0 && (
114-
<BaseBadge count={unreadReportCount} size="small" style={{ marginLeft: '5px' }} />
119+
<LiquidBlueBadge
120+
count={unreadReportCount}
121+
size="small"
122+
variant="tab"
123+
style={{ marginLeft: '5px' }}
124+
/>
115125
)}
116126
</span>
117127
),
@@ -154,9 +164,13 @@ export const NotificationsDropdown: React.FC = () => {
154164
<BaseButton
155165
type={isOpened ? 'ghost' : 'text'}
156166
icon={
157-
<BaseBadge count={totalUnreadCount > 0 ? totalUnreadCount : 0} overflowCount={99} dot={false}>
167+
<LiquidBlueBadge
168+
count={totalUnreadCount > 0 ? totalUnreadCount : 0}
169+
overflowCount={99}
170+
dot={false}
171+
>
158172
<BellOutlined />
159-
</BaseBadge>
173+
</LiquidBlueBadge>
160174
}
161175
/>
162176
</HeaderActionWrapper>

src/components/payment/PaymentNotifications/PaymentNotifications.styles.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,13 @@ export const PaymentCountBadge = styled(Badge)`
213213
margin-left: 0.5rem;
214214
215215
.ant-badge-count {
216-
background-color: var(--success-color);
216+
background: linear-gradient(135deg, #00ffff 0%, #06b6d4 100%);
217+
color: rgba(0, 0, 0, 0.85);
218+
font-weight: 600;
219+
box-shadow:
220+
0 0 8px rgba(0, 255, 255, 0.5),
221+
0 0 16px rgba(0, 255, 255, 0.3);
222+
border: none;
217223
}
218224
`;
219225

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import React, { useState } from 'react';
2+
import { Radio, Dropdown, Button, Space } from 'antd';
3+
import { DownOutlined } from '@ant-design/icons';
4+
import type { MenuProps } from 'antd';
5+
import styled from 'styled-components';
6+
7+
export type TimeRange = '1day' | '1week' | '1month' | '1year' | '2year' | '3year' | '5year' | 'all';
8+
9+
interface TimeRangeSelectorProps {
10+
value: TimeRange;
11+
onChange: (value: TimeRange) => void;
12+
}
13+
14+
const Container = styled.div`
15+
display: flex;
16+
gap: 0.5rem;
17+
flex-wrap: wrap;
18+
margin-bottom: 1rem;
19+
`;
20+
21+
const StyledRadioGroup = styled(Radio.Group)`
22+
display: flex;
23+
gap: 0.5rem;
24+
25+
.ant-radio-button-wrapper {
26+
background: rgba(0, 255, 255, 0.05);
27+
border: 1px solid rgba(0, 255, 255, 0.2);
28+
color: rgba(0, 255, 255, 0.8);
29+
font-size: 0.75rem;
30+
padding: 0.25rem 0.75rem;
31+
height: auto;
32+
line-height: 1.2;
33+
transition: all 0.3s ease;
34+
35+
&:hover {
36+
background: rgba(0, 255, 255, 0.1);
37+
border-color: rgba(0, 255, 255, 0.4);
38+
color: rgba(0, 255, 255, 1);
39+
}
40+
41+
&.ant-radio-button-wrapper-checked {
42+
background: rgba(0, 255, 255, 0.15);
43+
border-color: rgba(0, 255, 255, 0.6);
44+
color: #00ffff;
45+
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
46+
47+
&::before {
48+
background-color: rgba(0, 255, 255, 0.6);
49+
}
50+
}
51+
52+
&:not(:first-child)::before {
53+
background-color: rgba(0, 255, 255, 0.2);
54+
}
55+
}
56+
57+
@media (max-width: 768px) {
58+
.ant-radio-button-wrapper {
59+
font-size: 0.7rem;
60+
padding: 0.2rem 0.5rem;
61+
}
62+
}
63+
`;
64+
65+
const StyledDropdownButton = styled(Dropdown.Button)`
66+
.ant-btn {
67+
background: rgba(0, 255, 255, 0.05);
68+
border: 1px solid rgba(0, 255, 255, 0.2);
69+
color: rgba(0, 255, 255, 0.8);
70+
font-size: 0.75rem;
71+
padding: 0.25rem 0.75rem;
72+
height: auto;
73+
line-height: 1.2;
74+
transition: all 0.3s ease;
75+
76+
&:hover {
77+
background: rgba(0, 255, 255, 0.1);
78+
border-color: rgba(0, 255, 255, 0.4);
79+
color: rgba(0, 255, 255, 1);
80+
}
81+
82+
&.active {
83+
background: rgba(0, 255, 255, 0.15);
84+
border-color: rgba(0, 255, 255, 0.6);
85+
color: #00ffff;
86+
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
87+
}
88+
}
89+
90+
.ant-dropdown-trigger {
91+
padding: 0 8px;
92+
}
93+
94+
@media (max-width: 768px) {
95+
.ant-btn {
96+
font-size: 0.7rem;
97+
padding: 0.2rem 0.5rem;
98+
}
99+
}
100+
`;
101+
102+
const yearOptions = [
103+
{ label: '1 Year', value: '1year' },
104+
{ label: '2 Years', value: '2year' },
105+
{ label: '3 Years', value: '3year' },
106+
{ label: '5 Years', value: '5year' },
107+
{ label: 'All Time', value: 'all' },
108+
];
109+
110+
export const TimeRangeSelector: React.FC<TimeRangeSelectorProps> = ({ value, onChange }) => {
111+
const isYearRange = ['1year', '2year', '3year', '5year', 'all'].includes(value);
112+
const nonYearValue = !isYearRange ? value : undefined;
113+
114+
const [selectedYearLabel, setSelectedYearLabel] = useState(() => {
115+
const option = yearOptions.find(opt => opt.value === value);
116+
return option ? option.label : '1 Year';
117+
});
118+
119+
const handleYearSelect: MenuProps['onClick'] = ({ key }) => {
120+
const option = yearOptions.find(opt => opt.value === key);
121+
if (option) {
122+
setSelectedYearLabel(option.label);
123+
onChange(key as TimeRange);
124+
}
125+
};
126+
127+
const yearMenuItems: MenuProps['items'] = yearOptions.map(option => ({
128+
key: option.value,
129+
label: option.label,
130+
}));
131+
132+
const handleNonYearChange = (e: any) => {
133+
onChange(e.target.value as TimeRange);
134+
};
135+
136+
return (
137+
<Container>
138+
<StyledRadioGroup
139+
value={nonYearValue}
140+
onChange={handleNonYearChange}
141+
optionType="button"
142+
buttonStyle="solid"
143+
>
144+
<Radio.Button value="1day">1 Day</Radio.Button>
145+
<Radio.Button value="1week">1 Week</Radio.Button>
146+
<Radio.Button value="1month">1 Month</Radio.Button>
147+
</StyledRadioGroup>
148+
149+
<StyledDropdownButton
150+
menu={{ items: yearMenuItems, onClick: handleYearSelect }}
151+
className={isYearRange ? 'active' : ''}
152+
icon={<DownOutlined />}
153+
>
154+
{selectedYearLabel}
155+
</StyledDropdownButton>
156+
</Container>
157+
);
158+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { TimeRangeSelector } from './TimeRangeSelector';
2+
export type { TimeRange } from './TimeRangeSelector';

src/components/report/ReportNotifications/ReportNotifications.styles.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,13 @@ export const ReportCountBadge = styled(Badge)`
233233
margin-left: 0.5rem;
234234
235235
.ant-badge-count {
236-
background-color: var(--warning-color);
236+
background: linear-gradient(135deg, #00ffff 0%, #06b6d4 100%);
237+
color: rgba(0, 0, 0, 0.85);
238+
font-weight: 600;
239+
box-shadow:
240+
0 0 8px rgba(0, 255, 255, 0.5),
241+
0 0 16px rgba(0, 255, 255, 0.3);
242+
border: none;
237243
}
238244
`;
239245

0 commit comments

Comments
 (0)