Skip to content

Commit 6fa3ad6

Browse files
markrickertmorganick
authored andcommitted
Remove react-confirm-alert and use our own UI
1 parent cefdb24 commit 6fa3ad6

File tree

5 files changed

+168
-123
lines changed

5 files changed

+168
-123
lines changed

apps/reactotron-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
"immer": "^10.0.3",
5959
"lodash.debounce": "^4.0.8",
6060
"react": "18.2.0",
61-
"react-confirm-alert": "^3.0.6",
6261
"react-dom": "18.2.0",
62+
"react-fade-in": "^2.0.1",
6363
"react-ga4": "^2.1.0",
6464
"react-hotkeys": "^2.0.0",
6565
"react-icons": "^4.11.0",

apps/reactotron-app/src/renderer/App.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import styled from "styled-components"
44

55
import SideBar from "./components/SideBar"
66
import Footer from "./components/Footer"
7+
import AnalyticsOptOut from "./components/AnalyticsOptOut"
78
import RootContextProvider from "./contexts"
89
import RootModals from "./RootModals"
910

@@ -35,14 +36,27 @@ const AppContainer: React.FC<{ children: React.ReactNode }> = ({ children }) =>
3536
usePageTracking()
3637
const { initializeAnalytics } = useAnalytics()
3738

39+
const [showsAnalyticsInterface, setShowsAnalyticsInterface] = React.useState(false)
40+
3841
useEffect(() => {
3942
const timer = setTimeout(() => {
40-
initializeAnalytics()
43+
const status = initializeAnalytics()
44+
if (status === "unknown" && showsAnalyticsInterface === false) {
45+
// Show the user the interface to enable/disable analytics
46+
setShowsAnalyticsInterface(true)
47+
}
4148
}, 250)
4249
return () => clearTimeout(timer)
43-
}, [])
50+
}, [showsAnalyticsInterface])
4451

45-
return <AppContainerComponent>{children}</AppContainerComponent>
52+
return (
53+
<AppContainerComponent>
54+
{children}
55+
{showsAnalyticsInterface && (
56+
<AnalyticsOptOut onClose={() => setShowsAnalyticsInterface(false)} />
57+
)}
58+
</AppContainerComponent>
59+
)
4660
}
4761

4862
const TopSection = styled.div`
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React from "react"
2+
import styled, { useTheme } from "styled-components"
3+
import FadeIn from "react-fade-in"
4+
import { reactotronAnalytics } from "../../images"
5+
import configStore from "../../config"
6+
7+
const Overlay = styled.div`
8+
position: absolute;
9+
top: 0;
10+
bottom: 0;
11+
left: 0;
12+
right: 0;
13+
display: flex;
14+
background-color: ${(props) => props.theme.backgroundLighter};
15+
justify-content: center;
16+
align-items: center;
17+
`
18+
19+
const AlertContainer = styled.div`
20+
padding: 30px;
21+
text-align: left;
22+
border-radius: 10px;
23+
background: ${(props) => props.theme.backgroundDarker};
24+
color: ${(props) => props.theme.foregroundLight};
25+
26+
box-shadow: 0 20px 75px ${(props) => props.theme.backgroundSubtleLight};
27+
width: 80%;
28+
max-width: 500px;
29+
`
30+
31+
const AlertHeader = styled.div`
32+
display: flex;
33+
justify-content: center;
34+
margin-bottom: 20px;
35+
`
36+
37+
const AlertHeaderImage = styled.img`
38+
height: 128px;
39+
`
40+
41+
const ButtonGroup = styled.div`
42+
display: flex;
43+
flex-direction: column;
44+
justify-content: flex-start;
45+
margin-top: 20px;
46+
`
47+
48+
const Button = styled.button`
49+
outline: none;
50+
background: ${(props) => props.theme.backgroundLighter};
51+
border: none;
52+
display: inline-block;
53+
padding: 6px 18px;
54+
color: ${(props) => props.theme.background};
55+
margin-right: 10px;
56+
border-radius: 5px;
57+
font-size: 16px;
58+
cursor: pointer;
59+
`
60+
61+
// This is a custom alert that we use to ask the user if they want to opt-in to analytics
62+
// We use this instead of the default alert because we want to style it to match our app.
63+
const AnalyticsOptOut = ({ onClose }) => {
64+
const theme = useTheme()
65+
66+
return (
67+
<FadeIn wrapperTag={Overlay}>
68+
<AlertContainer>
69+
<AlertHeader>
70+
<AlertHeaderImage src={reactotronAnalytics} />
71+
</AlertHeader>
72+
<h1>Opt in to Reactotron analytics?</h1>
73+
<p>Help us improve Reactotron!</p>
74+
<p>
75+
We&apos;d like to collect anonymous usage data to enhance Reactotron&apos;s performance
76+
and features. This data includes general usage patterns and interactions. No personal
77+
information will be collected.
78+
</p>
79+
<p>
80+
You can change this setting at any time and by opting in, you can contribute to making
81+
Reactotron better for everyone!
82+
</p>
83+
<p>Would you like to participate?</p>
84+
<ButtonGroup>
85+
<Button
86+
onClick={() => {
87+
configStore.set("analyticsOptOut", true)
88+
onClose()
89+
}}
90+
style={{
91+
backgroundColor: theme.tag,
92+
}}
93+
>
94+
No, don&apos;t collect any data
95+
</Button>
96+
<Button
97+
onClick={() => {
98+
configStore.set("analyticsOptOut", false)
99+
onClose()
100+
}}
101+
style={{
102+
marginTop: 20,
103+
backgroundColor: theme.string,
104+
fontWeight: "bold",
105+
}}
106+
>
107+
Yes, I understand no personal information will be collected
108+
</Button>
109+
</ButtonGroup>
110+
</AlertContainer>
111+
</FadeIn>
112+
)
113+
}
114+
115+
export default AnalyticsOptOut
Lines changed: 25 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import React, { useCallback, useEffect, useState } from "react"
1+
import React from "react"
22
import ReactGA from "react-ga4"
33
import packageJson from "../../../package.json"
44
import { useLocation } from "react-router"
55
import configStore from "../config"
6-
import { confirmAlert } from "react-confirm-alert" // Import
7-
import { reactotronAnalytics } from "../images"
8-
import "react-confirm-alert/src/react-confirm-alert.css" // Import css
96

107
// This is the Google Analytics 4 key for Reactotron
118
// TODO: Change this to the correct key for production.
129
const GA4_KEY = "G-WZE3E5XCQ7"
1310

1411
type IAnalyticsEventCategory =
12+
| { category: "opt-out"; actions: ["opt-out"] }
1513
| { category: "android"; actions: ["settings", "reverse-tunnel", "reload-app", "shake-device"] }
1614
| { category: "navigation"; actions: ["keyboard_shortcut"] }
1715
| { category: "external_link"; actions: ["click"] }
@@ -43,10 +41,10 @@ type IOptOutStatus = "unknown" | true | false
4341
// It also handles the user's opt-out status.
4442
// We use a custom alert to ask the user if they want to opt-in to analytics.
4543
export const useAnalytics = () => {
46-
const [initialized, setInitialized] = useState(false)
47-
const [optedOut, setOptedOut] = useState<IOptOutStatus>("unknown")
44+
const [initialized, setInitialized] = React.useState(false)
45+
const [optedOut, setOptedOut] = React.useState<IOptOutStatus>("unknown")
4846

49-
useEffect(() => {
47+
React.useEffect(() => {
5048
const storeWatcher = configStore.onDidChange("analyticsOptOut", (newValue) => {
5149
console.log("[analytics] user has changed opt-out status", newValue)
5250
setOptedOut(newValue as IOptOutStatus)
@@ -62,24 +60,21 @@ export const useAnalytics = () => {
6260

6361
if (status === "unknown") {
6462
console.log(`[analytics] user has not opted in or out`)
65-
confirmAlert({
66-
closeOnEscape: false,
67-
closeOnClickOutside: false,
68-
customUI: CustomAlert,
69-
})
7063
} else {
7164
// If the user has opted out, we'll disable analytics
7265
setOptedOut(status)
7366
setInitialized(false)
7467
console.log(`[analytics] user has opted ${status ? "out" : "in"}`)
7568
}
69+
70+
return status
7671
}
7772

7873
// Initialize analytics and set some system data like the app version and platform
7974
// as well as the mode we are running in. We don't want to send analytics events
8075
// during tests, so we disable them if we are running in test mode.
8176
// We also disable analytics if the user has opted out.
82-
useEffect(() => {
77+
React.useEffect(() => {
8378
const initialize = () => {
8479
const testMode = process.env.NODE_ENV === "test" // we don't want to send analytics events during tests
8580
ReactGA.initialize(GA4_KEY, { testMode: testMode || optedOut === true })
@@ -102,7 +97,7 @@ export const useAnalytics = () => {
10297
// This is the main function we use to send events throughout the app.
10398
// See documentation here for how to use react-ga4:
10499
// https://github.com/codler/react-ga4
105-
const sendAnalyticsEvent = useCallback(
100+
const sendAnalyticsEvent = React.useCallback(
106101
(event: UaEventOptions) => {
107102
if (!optedOut) {
108103
console.log("[analytics] Sending event", event)
@@ -113,7 +108,7 @@ export const useAnalytics = () => {
113108
)
114109

115110
// Send a page view event
116-
const sendPageViewAnalyticsEvent = useCallback(
111+
const sendPageViewAnalyticsEvent = React.useCallback(
117112
(page: string) => {
118113
if (!optedOut) {
119114
console.log("[analytics] Sending page view event", page)
@@ -124,7 +119,7 @@ export const useAnalytics = () => {
124119
)
125120

126121
// Send a keyboard shortcut event
127-
const sendKeyboardShortcutAnalyticsEvent = useCallback(
122+
const sendKeyboardShortcutAnalyticsEvent = React.useCallback(
128123
(label: string) => {
129124
sendAnalyticsEvent({
130125
category: "navigation",
@@ -137,7 +132,7 @@ export const useAnalytics = () => {
137132
)
138133

139134
// Send a custom command event
140-
const sendCustomCommandAnalyticsEvent = useCallback(
135+
const sendCustomCommandAnalyticsEvent = React.useCallback(
141136
(command: string) => {
142137
sendAnalyticsEvent({
143138
category: "custom_command",
@@ -150,7 +145,7 @@ export const useAnalytics = () => {
150145
)
151146

152147
// Send an external link event
153-
const sendExternalLinkAnalyticsEvent = useCallback(
148+
const sendExternalLinkAnalyticsEvent = React.useCallback(
154149
(label: string) => {
155150
sendAnalyticsEvent({
156151
category: "external_link",
@@ -162,13 +157,24 @@ export const useAnalytics = () => {
162157
[sendAnalyticsEvent]
163158
)
164159

160+
const sendOptOutAnalyticsEvent = React.useCallback(() => {
161+
const event = {
162+
category: "opt-out",
163+
action: "opt-out",
164+
nonInteraction: false,
165+
}
166+
console.log("[analytics] Sending opt-out event", event)
167+
ReactGA.event(event) // this is the only time we send an event without checking the optedOut status
168+
}, [])
169+
165170
return {
166171
initializeAnalytics,
167172
sendAnalyticsEvent,
168173
sendPageViewAnalyticsEvent,
169174
sendKeyboardShortcutAnalyticsEvent,
170175
sendCustomCommandAnalyticsEvent,
171176
sendExternalLinkAnalyticsEvent,
177+
sendOptOutAnalyticsEvent,
172178
}
173179
}
174180

@@ -179,96 +185,7 @@ export const usePageTracking = () => {
179185
const location = useLocation()
180186
const { sendPageViewAnalyticsEvent } = useAnalytics()
181187

182-
useEffect(() => {
188+
React.useEffect(() => {
183189
sendPageViewAnalyticsEvent(location.pathname)
184190
}, [location, sendPageViewAnalyticsEvent])
185191
}
186-
187-
// This is a custom alert that we use to ask the user if they want to opt-in to analytics
188-
// We use this instead of the default alert because we want to style it to match our app.
189-
// We inherit the styles from react-confirm-alert and override them as needed.
190-
// Unfortunately, we can't use styled-components here because react-confirm-alert doesn't support it.
191-
// This also means we have to hard-code the colors here instead of using our theme, which is not ideal.
192-
const CustomAlert = ({ onClose }) => {
193-
return (
194-
<div
195-
className="react-confirm-alert-overlay"
196-
style={{
197-
background: "#1e1e1e",
198-
}}
199-
>
200-
<div
201-
className="react-confirm-alert-body"
202-
style={{
203-
background: "#1e1e1e",
204-
color: "#c3c3c3",
205-
boxShadow: "0 20px 75px rgba(255, 255, 255, 0.13)",
206-
width: "80%",
207-
maxWidth: "500px",
208-
}}
209-
>
210-
<div
211-
style={{
212-
display: "flex",
213-
justifyContent: "center",
214-
marginBottom: 20,
215-
}}
216-
>
217-
<img
218-
src={reactotronAnalytics}
219-
style={{
220-
height: 128,
221-
}}
222-
/>
223-
</div>
224-
<h1>Opt in to Reactotron analytics?</h1>
225-
<p>Help us improve Reactotron!</p>
226-
<p>
227-
We&apos;d like to collect anonymous usage data to enhance Reactotron&apos;s performance
228-
and features. This data includes general usage patterns and interactions. No personal
229-
information will be collected.
230-
</p>
231-
<p>
232-
You can change this setting at any time and by opting in, you can contribute to making
233-
Reactotron better for everyone!
234-
</p>
235-
<p>Would you like to participate?</p>
236-
<div
237-
className="react-confirm-alert-button-group"
238-
style={{
239-
flexDirection: "column",
240-
}}
241-
>
242-
<button
243-
onClick={() => {
244-
configStore.set("analyticsOptOut", true)
245-
onClose()
246-
}}
247-
style={{
248-
fontSize: 16,
249-
backgroundColor: "#cf6a4c",
250-
color: "#1e1e1e",
251-
}}
252-
>
253-
No, don&apos;t collect any data
254-
</button>
255-
<button
256-
onClick={() => {
257-
configStore.set("analyticsOptOut", false)
258-
onClose()
259-
}}
260-
style={{
261-
fontSize: 16,
262-
marginTop: 20,
263-
backgroundColor: "#8f9d6a",
264-
color: "#1e1e1e",
265-
fontWeight: "bold",
266-
}}
267-
>
268-
Yes, I understand no personal information will be collected
269-
</button>
270-
</div>
271-
</div>
272-
</div>
273-
)
274-
}

0 commit comments

Comments
 (0)