Skip to content

Commit 4a1248e

Browse files
committed
fix(#3681): preserve maintenance banner expanded state
1 parent 36aa140 commit 4a1248e

File tree

10 files changed

+169
-44
lines changed

10 files changed

+169
-44
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ changes.
1212

1313
### Added
1414

15+
- Preserve maintenance ending banner state on the wallet connection change [Issue 3681](https://github.com/IntersectMBO/govtool/issues/3681)
16+
1517
### Fixed
1618

1719
### Changed
@@ -20,8 +22,8 @@ changes.
2022

2123
## [v2.0.23](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.23) 2025-05-22
2224

23-
2425
### Added
26+
2527
- Add CIP-129 support for gov_actions hashes in Live Voting (governance actions) [Issue 3619](https://github.com/IntersectMBO/govtool/issues/3619)
2628

2729
- Add maintenance ending banner [Issue 3647](https://github.com/IntersectMBO/govtool/issues/3647)

govtool/frontend/src/components/organisms/Drawer.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { useFeatureFlag } from "@context";
77
import { useGetVoterInfo } from "@hooks";
88
import { WalletInfoCard, DRepInfoCard } from "@molecules";
99
import { openInNewTab } from "@utils";
10+
import { useMaintenanceEndingBannerContext } from "./MaintenanceEndingBanner";
1011

1112
export const Drawer = () => {
1213
const {
1314
isProposalDiscussionForumEnabled,
1415
isGovernanceOutcomesPillarEnabled,
1516
} = useFeatureFlag();
1617
const { voter } = useGetVoterInfo();
18+
const { height: maintenanceEndingBannerHeight } =
19+
useMaintenanceEndingBannerContext();
1720

1821
return (
1922
<Box
@@ -23,7 +26,7 @@ export const Drawer = () => {
2326
flexDirection: "column",
2427
height: "100vh",
2528
position: "sticky",
26-
top: 0,
29+
top: maintenanceEndingBannerHeight,
2730
width: `${DRAWER_WIDTH}px`,
2831

2932
overflowY: "auto",

govtool/frontend/src/components/organisms/MaintenanceEndingBanner.tsx renamed to govtool/frontend/src/components/organisms/MaintenanceEndingBanner/MaintenanceEndingBanner.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { Box, Typography, IconButton } from "@mui/material";
2-
import { useState } from "react";
32
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
43
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
54
import { Trans, useTranslation } from "react-i18next";
5+
import { useMaintenanceEndingBannerContext } from "./MaintenanceEndingBannerContext";
6+
7+
const EXPANDED_HEIGHT = 135;
8+
const COLLAPSED_HEIGHT = 50;
69

710
export const MaintenanceEndingBanner = () => {
8-
const [isExpanded, setIsExpanded] = useState(true);
11+
const { ref, isExpanded, toggleExpanded } =
12+
useMaintenanceEndingBannerContext();
913
const { t } = useTranslation();
1014

11-
const handleToggle = () => {
12-
setIsExpanded((prev) => !prev);
13-
};
14-
1515
return (
1616
<Box
17+
ref={ref}
1718
sx={{
1819
backgroundColor: "#9c2224",
1920
width: "100%",
@@ -44,7 +45,7 @@ export const MaintenanceEndingBanner = () => {
4445
</Box>
4546
<Box sx={{ display: "flex" }}>
4647
<IconButton
47-
onClick={handleToggle}
48+
onClick={toggleExpanded}
4849
size="small"
4950
data-testid="toggle-maintenance-banner"
5051
sx={{
@@ -60,7 +61,7 @@ export const MaintenanceEndingBanner = () => {
6061
{/* Expandable Content */}
6162
<Box
6263
sx={{
63-
maxHeight: isExpanded ? "500px" : "0px",
64+
height: isExpanded ? EXPANDED_HEIGHT - COLLAPSED_HEIGHT : "0px",
6465
transition: "max-height 0.3s ease-in-out",
6566
overflow: "hidden",
6667
}}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
createContext,
3+
createRef,
4+
useContext,
5+
useMemo,
6+
useRef,
7+
useState,
8+
useLayoutEffect,
9+
} from "react";
10+
11+
interface MaintenanceEndingBannerContextType {
12+
ref: React.RefObject<HTMLDivElement>;
13+
height: number;
14+
y: number;
15+
isExpanded: boolean;
16+
toggleExpanded: () => void;
17+
}
18+
19+
const MaintenanceEndingBannerContext =
20+
createContext<MaintenanceEndingBannerContextType>({
21+
ref: createRef<HTMLDivElement>(),
22+
height: 0,
23+
y: 0,
24+
isExpanded: true,
25+
toggleExpanded: () => {},
26+
});
27+
28+
export const MaintenanceEndingBannerProvider = ({
29+
children,
30+
}: React.PropsWithChildren) => {
31+
const ref = useRef<HTMLDivElement>(null);
32+
const [height, setHeight] = useState(0);
33+
const [y, setY] = useState(0);
34+
const [isExpanded, setIsExpanded] = useState(true);
35+
36+
const toggleExpanded = () => {
37+
setIsExpanded((prev) => !prev);
38+
};
39+
40+
useLayoutEffect(() => {
41+
let frameId: number | null = null;
42+
43+
const updatePosition = () => {
44+
if (ref.current) {
45+
const rect = ref.current.getBoundingClientRect();
46+
47+
setY(rect.top);
48+
setHeight(rect.height);
49+
}
50+
};
51+
52+
const throttledUpdate = () => {
53+
// Context skipping update - frame already queued
54+
if (frameId) return;
55+
56+
frameId = requestAnimationFrame(() => {
57+
updatePosition();
58+
frameId = null;
59+
});
60+
};
61+
62+
updatePosition();
63+
64+
window.addEventListener("scroll", throttledUpdate, { passive: true });
65+
window.addEventListener("resize", throttledUpdate);
66+
67+
return () => {
68+
window.removeEventListener("scroll", throttledUpdate);
69+
window.removeEventListener("resize", throttledUpdate);
70+
71+
if (frameId) {
72+
cancelAnimationFrame(frameId);
73+
}
74+
};
75+
}, []);
76+
77+
const value = useMemo(
78+
() => ({
79+
ref,
80+
height,
81+
y,
82+
isExpanded,
83+
toggleExpanded,
84+
}),
85+
[ref, height, y, isExpanded, toggleExpanded],
86+
);
87+
88+
return (
89+
<MaintenanceEndingBannerContext.Provider value={value}>
90+
{children}
91+
</MaintenanceEndingBannerContext.Provider>
92+
);
93+
};
94+
95+
export const useMaintenanceEndingBannerContext = () => {
96+
const context = useContext(MaintenanceEndingBannerContext);
97+
if (!context) {
98+
throw new Error(
99+
"useMaintenanceEndingBannerContext must be used within a MaintenanceEndingBannerProvider",
100+
);
101+
}
102+
return context;
103+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./MaintenanceEndingBanner";
2+
export * from "./MaintenanceEndingBannerContext";

govtool/frontend/src/components/organisms/TopBanners.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Box, Link, Typography } from "@mui/material";
22
import { Trans, useTranslation } from "react-i18next";
3-
import { useAppContext, useCardano } from "@/context";
3+
import { useAppContext } from "@/context";
44
import { LINKS } from "@/consts/links";
55
import { MaintenanceEndingBanner } from "./MaintenanceEndingBanner";
66

77
export const TopBanners = () => {
88
const { isMainnet, networkName, isInBootstrapPhase, isAppInitializing } =
99
useAppContext();
10-
const { isEnabled } = useCardano();
1110
const { t } = useTranslation();
1211

1312
if (isAppInitializing) {
@@ -49,18 +48,16 @@ export const TopBanners = () => {
4948
</Box>
5049

5150
{/* GOVTOOL MAINTENANCE ENDING SOON BANNER */}
52-
{isEnabled && (
53-
<Box
54-
sx={{
55-
position: "sticky",
56-
top: 0,
57-
width: "100%",
58-
zIndex: 1200,
59-
}}
60-
>
61-
<MaintenanceEndingBanner />
62-
</Box>
63-
)}
51+
<Box
52+
sx={{
53+
position: "sticky",
54+
top: 0,
55+
width: "100%",
56+
zIndex: 1200,
57+
}}
58+
>
59+
<MaintenanceEndingBanner />
60+
</Box>
6461

6562
{/* BOOTSTRAPPING BANNER */}
6663
<Box>

govtool/frontend/src/components/organisms/TopNav.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useCardano, useFeatureFlag, useModal } from "@context";
88
import { useScreenDimension, useTranslation } from "@hooks";
99
import { openInNewTab } from "@utils";
1010
import { DrawerMobile } from "./DrawerMobile";
11-
import { MaintenanceEndingBanner } from "./MaintenanceEndingBanner";
11+
import { useMaintenanceEndingBannerContext } from "./MaintenanceEndingBanner";
1212

1313
const POSITION_TO_BLUR = 80;
1414

@@ -26,6 +26,9 @@ export const TopNav = ({ isConnectButton = true }) => {
2626
const navigate = useNavigate();
2727
const { t } = useTranslation();
2828

29+
const { height: maintenanceEndingBannerHeight } =
30+
useMaintenanceEndingBannerContext();
31+
2932
useEffect(() => {
3033
const onScroll = () => {
3134
if (!containerRef.current?.nextElementSibling) return;
@@ -137,9 +140,14 @@ export const TopNav = ({ isConnectButton = true }) => {
137140
);
138141

139142
return (
140-
<Box sx={{ position: "sticky", top: 0, zIndex: 100, width: "100%" }}>
141-
<MaintenanceEndingBanner />
142-
143+
<Box
144+
sx={{
145+
position: "sticky",
146+
top: maintenanceEndingBannerHeight,
147+
zIndex: 100,
148+
width: "100%",
149+
}}
150+
>
143151
<AppBar
144152
ref={containerRef}
145153
position="static"

govtool/frontend/src/components/organisms/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export * from "./ValidatedGovernanceActionCard";
2929
export * from "./ValidatedGovernanceVotedOnCard";
3030
export * from "./VoteContext";
3131
export * from "./WrongRouteInfo";
32+
export * from "./MaintenanceEndingBanner";

govtool/frontend/src/context/contextProviders.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,31 @@ import { FeatureFlagProvider } from "./featureFlag";
77
import { GovernanceActionProvider } from "./governanceAction";
88
import { AdaHandleProvider } from "./adaHandle";
99
import { ProposalDiscussionProvider } from "./proposalDiscussion";
10+
import { MaintenanceEndingBannerProvider } from "@/components/organisms/MaintenanceEndingBanner/MaintenanceEndingBannerContext";
1011

1112
interface Props {
1213
children: React.ReactNode;
1314
}
1415

1516
const ContextProviders = ({ children }: Props) => (
1617
<AppContextProvider>
17-
<GovernanceActionProvider>
18-
<ProposalDiscussionProvider>
19-
<FeatureFlagProvider>
20-
<AdaHandleProvider>
21-
<ModalProvider>
22-
<SnackbarProvider>
23-
<DataActionsBarProvider>
24-
<CardanoProvider>{children}</CardanoProvider>
25-
</DataActionsBarProvider>
26-
</SnackbarProvider>
27-
</ModalProvider>
28-
</AdaHandleProvider>
29-
</FeatureFlagProvider>
30-
</ProposalDiscussionProvider>
31-
</GovernanceActionProvider>
18+
<MaintenanceEndingBannerProvider>
19+
<GovernanceActionProvider>
20+
<ProposalDiscussionProvider>
21+
<FeatureFlagProvider>
22+
<AdaHandleProvider>
23+
<ModalProvider>
24+
<SnackbarProvider>
25+
<DataActionsBarProvider>
26+
<CardanoProvider>{children}</CardanoProvider>
27+
</DataActionsBarProvider>
28+
</SnackbarProvider>
29+
</ModalProvider>
30+
</AdaHandleProvider>
31+
</FeatureFlagProvider>
32+
</ProposalDiscussionProvider>
33+
</GovernanceActionProvider>
34+
</MaintenanceEndingBannerProvider>
3235
</AppContextProvider>
3336
);
3437

govtool/frontend/src/pages/Dashboard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ export const Dashboard = () => {
8888

8989
return (
9090
<Background opacity={0.7}>
91-
<Box sx={{ display: "flex", position: "relative" }}>
91+
<Box
92+
sx={{
93+
display: "flex",
94+
position: "relative",
95+
}}
96+
>
9297
{isMobile ? null : <Drawer />}
9398
<Box
9499
sx={{

0 commit comments

Comments
 (0)