Skip to content

Commit 7ab675d

Browse files
committed
feat: Web Interstitial or Intent for app download
1 parent 2528417 commit 7ab675d

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import classes from "./App.module.css";
44

55
import { Box, Stack } from "@mantine/core";
66
import { Spotlight } from "@mantine/spotlight";
7+
import { WebInterstitial } from "./components/WebInterstitial";
78
import { loader as monacoLoader } from "@monaco-editor/react";
89
import { relaunch } from "@tauri-apps/plugin-process";
910
import { check } from "@tauri-apps/plugin-updater";
@@ -185,6 +186,7 @@ function App() {
185186
}}
186187
actions={spotlightActions}
187188
/>
189+
<WebInterstitial />
188190
</Box>
189191
);
190192
}

src/components/WebInterstitial.tsx

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import React, { useEffect, useState } from "react";
2+
import {
3+
Modal,
4+
Button,
5+
Text,
6+
Title,
7+
Group,
8+
Checkbox,
9+
Stack,
10+
Anchor,
11+
List,
12+
ThemeIcon,
13+
Divider,
14+
Badge,
15+
} from "@mantine/core";
16+
import { BsDownload, BsCheckCircle, BsStarFill } from "react-icons/bs";
17+
import { isTauri } from "@/utils/isTauri";
18+
import { APP_CONFIG } from "@/constants/app";
19+
20+
const STORAGE_KEY = "devbox_web_warning_dismissed";
21+
const STORAGE_KEY_DONT_SHOW = STORAGE_KEY + "_dont_show";
22+
23+
export const WebInterstitial: React.FC = () => {
24+
const [opened, setOpened] = useState(false);
25+
const [dontShowAgain, setDontShowAgain] = useState(false);
26+
const smallScreen = typeof window !== "undefined" && window.innerWidth <= 720;
27+
28+
useEffect(() => {
29+
try {
30+
if (isTauri()) return; // don't show inside Tauri
31+
const dismissed = localStorage.getItem(STORAGE_KEY);
32+
const dontShow = localStorage.getItem(STORAGE_KEY_DONT_SHOW);
33+
34+
const isMoreThan15Days = dismissed
35+
? new Date(dismissed).getTime() - new Date().getTime() > 15 * 24 * 60 * 60 * 1000
36+
: true;
37+
38+
if (isMoreThan15Days || (smallScreen && !dontShow)) {
39+
const t = setTimeout(() => setOpened(true), 300);
40+
return () => clearTimeout(t);
41+
}
42+
} catch (err) {
43+
// ignore
44+
}
45+
}, []);
46+
47+
const persistAndClose = (persist = false) => {
48+
try {
49+
if (persist) {
50+
const date = new Date().toISOString();
51+
localStorage.setItem(STORAGE_KEY, date);
52+
}
53+
if (dontShowAgain) {
54+
localStorage.setItem(STORAGE_KEY_DONT_SHOW, "1");
55+
}
56+
} catch (err) {
57+
// ignore
58+
}
59+
setOpened(false);
60+
};
61+
62+
const openReleases = () => {
63+
const url = APP_CONFIG.RELEASES_URL + "/latest";
64+
window.open(url, "_blank", "noopener,noreferrer");
65+
};
66+
67+
const openRepo = () => {
68+
const url = APP_CONFIG.PROJECT_URL;
69+
window.open(url, "_blank", "noopener,noreferrer");
70+
};
71+
72+
if (isTauri()) return null;
73+
74+
return (
75+
<Modal
76+
opened={opened}
77+
onClose={() => persistAndClose(true)}
78+
title={
79+
<Title order={4}>
80+
{smallScreen ? "Not optimzed for smaller screens" : "Thank you for using Devbox"}
81+
</Title>
82+
}
83+
centered
84+
withCloseButton
85+
closeOnEscape
86+
size="lg"
87+
aria-labelledby="web-warning-title"
88+
>
89+
<Stack gap="sm">
90+
<div style={{ gap: 16 }}>
91+
<div style={{ flex: 1, minWidth: 260 }}>
92+
<Text size="sm">
93+
Thanks for using{" "}
94+
<Button variant="transparent" h="fit-content" p={0} onClick={openRepo}>
95+
Devbox
96+
</Button>{" "}
97+
— this is best experienced as the desktop app. The desktop version provides a more
98+
polished editor experience, native integrations, and offline access to your tools.
99+
</Text>
100+
101+
<List spacing="xs" size="sm" mt="sm" withPadding>
102+
<List.Item
103+
icon={
104+
<ThemeIcon size={18} radius="xl" variant="light">
105+
<BsCheckCircle />
106+
</ThemeIcon>
107+
}
108+
>
109+
Faster editor performance and syntax highlighting
110+
</List.Item>
111+
<List.Item
112+
icon={
113+
<ThemeIcon size={18} radius="xl" variant="light">
114+
<BsCheckCircle />
115+
</ThemeIcon>
116+
}
117+
>
118+
Native file system access and drag/drop support
119+
</List.Item>
120+
<List.Item
121+
icon={
122+
<ThemeIcon size={18} radius="xl" variant="light">
123+
<BsCheckCircle />
124+
</ThemeIcon>
125+
}
126+
>
127+
Offline usage and automatic updates via the updater
128+
</List.Item>
129+
</List>
130+
</div>
131+
<Group mt={18} justify="flex-end">
132+
<Anchor
133+
size="sm"
134+
href={APP_CONFIG.RELEASES_URL + "/latest"}
135+
target="_blank"
136+
rel="noopener noreferrer"
137+
>
138+
Latest desktop release
139+
</Anchor>
140+
<Badge variant="filled">Recommended</Badge>
141+
</Group>
142+
</div>
143+
144+
<Divider />
145+
146+
<Text size="sm">
147+
If Devbox helped you build something useful, please consider supporting the project by
148+
starring the repository or opening issues/feedback so we can prioritize improvements.
149+
</Text>
150+
151+
<Group justify="space-between" style={{ marginTop: 12, alignItems: "center" }}>
152+
<Checkbox
153+
checked={dontShowAgain}
154+
onChange={e => setDontShowAgain((e.target as HTMLInputElement).checked)}
155+
label="Don't show this again"
156+
aria-label="Don't show web warning again"
157+
/>
158+
<Anchor
159+
href={APP_CONFIG.RELEASES_URL}
160+
size="sm"
161+
target="_blank"
162+
rel="noopener noreferrer"
163+
>
164+
View releases
165+
</Anchor>
166+
</Group>
167+
168+
<Group justify="flex-end" gap="sm" style={{ marginTop: 12, flexWrap: "wrap" }}>
169+
<Button variant="default" onClick={() => persistAndClose(true)} size="xs">
170+
Continue on web
171+
</Button>
172+
<Button
173+
variant="light"
174+
onClick={() => {
175+
openReleases();
176+
persistAndClose(true);
177+
}}
178+
size="xs"
179+
>
180+
<span style={{ display: "inline-flex", alignItems: "center" }}>
181+
<BsDownload style={{ marginRight: 8 }} />
182+
Download
183+
</span>
184+
</Button>
185+
<Button
186+
color="yellow"
187+
onClick={() => {
188+
openRepo();
189+
persistAndClose(true);
190+
}}
191+
size="xs"
192+
>
193+
<span style={{ display: "inline-flex", alignItems: "center" }}>
194+
<BsStarFill style={{ marginRight: 8 }} />
195+
Star
196+
</span>
197+
</Button>
198+
</Group>
199+
</Stack>
200+
</Modal>
201+
);
202+
};

0 commit comments

Comments
 (0)