Skip to content

Commit a9a482f

Browse files
Use localStorage for Framna popup (show once per month)
Replace sessionStorage with localStorage so the popup only shows once per month rather than once per session. Co-authored-by: Piotr Mionskowski <miensol@users.noreply.github.com>
1 parent a740a66 commit a9a482f

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import React, { useEffect, useState } from 'react'
2+
import styled from 'styled-components'
3+
4+
const STORAGE_KEY = 'framna_announcement_shown_at'
5+
const ONE_MONTH_MS = 30 * 24 * 60 * 60 * 1000
6+
const SHOW_FROM_DATE = new Date('2026-03-18T00:00:00')
7+
8+
function shouldShowPopup(): boolean {
9+
const isStaging = process.env.GATSBY_ACTIVE_ENV === 'staging'
10+
const now = new Date()
11+
12+
if (!isStaging && now < SHOW_FROM_DATE) {
13+
return false
14+
}
15+
16+
try {
17+
const storedAt = localStorage.getItem(STORAGE_KEY)
18+
if (!storedAt) return true
19+
const lastShown = parseInt(storedAt, 10)
20+
if (isNaN(lastShown)) return true
21+
return Date.now() - lastShown > ONE_MONTH_MS
22+
} catch {
23+
return true
24+
}
25+
}
26+
27+
function markPopupShown(): void {
28+
try {
29+
localStorage.setItem(STORAGE_KEY, String(Date.now()))
30+
} catch {
31+
// ignore
32+
}
33+
}
34+
35+
const Overlay = styled.div`
36+
position: fixed;
37+
inset: 0;
38+
background: rgba(0, 0, 0, 0.5);
39+
display: flex;
40+
align-items: center;
41+
justify-content: center;
42+
z-index: 9999;
43+
padding: 16px;
44+
`
45+
46+
const Modal = styled.div`
47+
display: flex;
48+
max-width: 640px;
49+
width: 100%;
50+
border-radius: 16px;
51+
overflow: hidden;
52+
background: #ffffff;
53+
position: relative;
54+
`
55+
56+
const LeftPanel = styled.div`
57+
background: #5ee03a;
58+
flex: 0 0 45%;
59+
display: flex;
60+
align-items: center;
61+
justify-content: center;
62+
padding: 40px 24px;
63+
`
64+
65+
const FramnaLogo = styled.img`
66+
max-width: 180px;
67+
width: 100%;
68+
height: auto;
69+
`
70+
71+
const RightPanel = styled.div`
72+
flex: 1;
73+
padding: 48px 40px 40px;
74+
display: flex;
75+
flex-direction: column;
76+
justify-content: center;
77+
gap: 16px;
78+
`
79+
80+
const Title = styled.h2`
81+
font-size: 28px;
82+
font-weight: 700;
83+
line-height: 1.2;
84+
color: #0a0a0a;
85+
margin: 0;
86+
`
87+
88+
const Body = styled.p`
89+
font-size: 16px;
90+
line-height: 1.6;
91+
color: #0a0a0a;
92+
margin: 0;
93+
`
94+
95+
const VisitButton = styled.a`
96+
display: inline-block;
97+
background: #0a0a0a;
98+
color: #ffffff;
99+
font-size: 16px;
100+
font-weight: 600;
101+
padding: 14px 28px;
102+
border-radius: 100px;
103+
text-decoration: none;
104+
align-self: flex-start;
105+
margin-top: 8px;
106+
107+
&:hover {
108+
background: #333333;
109+
}
110+
`
111+
112+
const CloseButton = styled.button`
113+
position: absolute;
114+
top: 16px;
115+
right: 16px;
116+
background: none;
117+
border: none;
118+
cursor: pointer;
119+
padding: 4px;
120+
display: flex;
121+
align-items: center;
122+
justify-content: center;
123+
color: #0a0a0a;
124+
125+
&:hover {
126+
opacity: 0.6;
127+
}
128+
`
129+
130+
const FramnaAnnouncementPopup: React.FC = () => {
131+
const [visible, setVisible] = useState(false)
132+
133+
useEffect(() => {
134+
if (!shouldShowPopup()) return
135+
136+
const show = () => {
137+
setVisible(true)
138+
markPopupShown()
139+
}
140+
141+
if (document.readyState === 'complete') {
142+
const timer = setTimeout(show, 500)
143+
return () => clearTimeout(timer)
144+
} else {
145+
const onLoad = () => {
146+
const timer = setTimeout(show, 500)
147+
window.removeEventListener('load', onLoad)
148+
return () => clearTimeout(timer)
149+
}
150+
window.addEventListener('load', onLoad)
151+
return () => window.removeEventListener('load', onLoad)
152+
}
153+
}, [])
154+
155+
if (!visible) return null
156+
157+
return (
158+
<Overlay onClick={() => setVisible(false)}>
159+
<Modal onClick={e => e.stopPropagation()}>
160+
<LeftPanel>
161+
<FramnaLogo src='/images/why-us/timeline/framna.svg' alt='Framna logo' />
162+
</LeftPanel>
163+
<RightPanel>
164+
<Title>Bright Inventions is now Framna</Title>
165+
<Body>
166+
We partner with industry leaders (and those about to be) to create digital products that
167+
define markets, reshape industries, and drive meaningful growth.
168+
</Body>
169+
<VisitButton href='https://framna.com/' target='_blank' rel='noopener noreferrer'>
170+
Visit Framna
171+
</VisitButton>
172+
</RightPanel>
173+
<CloseButton onClick={() => setVisible(false)} aria-label='Close'>
174+
<svg width='20' height='20' viewBox='0 0 20 20' fill='none'>
175+
<path
176+
d='M15 5L5 15M5 5l10 10'
177+
stroke='currentColor'
178+
strokeWidth='2'
179+
strokeLinecap='round'
180+
/>
181+
</svg>
182+
</CloseButton>
183+
</Modal>
184+
</Overlay>
185+
)
186+
}
187+
188+
export default FramnaAnnouncementPopup

src/layout/Page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useLocation } from '@reach/router'
1111
import { MDXComponentsWrapper } from '../mdx'
1212
import CookiesNotice from '../analytics/cookies-notice'
1313
import { useTranslation } from 'react-i18next'
14+
import FramnaAnnouncementPopup from '../components/shared/FramnaAnnouncementPopup'
1415

1516
export const Page: React.FC<PropsWithChildren<{ className?: string }>> = ({ children, className }) => {
1617
const [mobileMenuOpened, setMobileMenuOpened] = useState(false)
@@ -35,6 +36,7 @@ export const Page: React.FC<PropsWithChildren<{ className?: string }>> = ({ chil
3536
<MDXComponentsWrapper>{children}</MDXComponentsWrapper>
3637
<CookiesNotice />
3738
<Footer />
39+
<FramnaAnnouncementPopup />
3840
</div>
3941
)
4042
}

0 commit comments

Comments
 (0)