Skip to content

Commit a4c70eb

Browse files
Redirect potentially non Next Gen users to prototype version (#247)
- Shown to users before browser compatibility warning dialog - Shown only if they are detected to be in the UK (GB), Isle of Man (IM), Jersey (JE), or Guernsey (GG) - Should only be shown once to the user - API proxy for calling browser info endpoint for local dev via .env
1 parent fce8fda commit a4c70eb

File tree

6 files changed

+163
-31
lines changed

6 files changed

+163
-31
lines changed

src/App.svelte

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import PageContentView from './views/PageContentView.svelte';
1010
import {
1111
compatibility,
12+
hasSeenAppVersionRedirectDialog,
1213
isCompatibilityWarningDialogOpen,
1314
} from './script/stores/uiStore';
1415
import IncompatiblePlatformView from './views/IncompatiblePlatformView.svelte';
1516
import CompatibilityWarningDialog from './components/CompatibilityWarningDialog.svelte';
17+
import AppVersionRedirectDialog from './components/AppVersionRedirectDialog.svelte';
1618
import Router from './router/Router.svelte';
1719
import ControlBar from './components/control-bar/ControlBar.svelte';
1820
import { t } from './i18n';
@@ -31,8 +33,17 @@
3133
connectionDialogState,
3234
} from './script/stores/connectDialogStore';
3335
import { isLoading } from 'svelte-i18n';
36+
import { fetchBrowserInfo } from './script/utils/api';
37+
import { get } from 'svelte/store';
38+
39+
let isPotentiallyNonNextGenUser: boolean = false;
40+
onMount(async () => {
41+
if (!get(hasSeenAppVersionRedirectDialog)) {
42+
const { country } = await fetchBrowserInfo();
43+
const nextGenAvailableCountries = ['GB', 'JE', 'IM', 'GG'];
44+
isPotentiallyNonNextGenUser = !nextGenAvailableCountries.includes(country || '');
45+
}
3446
35-
onMount(() => {
3647
const { bluetooth, usb } = $compatibility;
3748
// Value must switch from false to true after mount to trigger dialog transition
3849
isCompatibilityWarningDialogOpen.set(!bluetooth && !usb);
@@ -65,6 +76,9 @@
6576
{#if $consent}
6677
<CompatibilityWarningDialog />
6778
{/if}
79+
{#if $consent && !$isCompatibilityWarningDialogOpen && isPotentiallyNonNextGenUser}
80+
<AppVersionRedirectDialog />
81+
{/if}
6882
<div class="w-full flex flex-col bg-backgrounddark">
6983
<ControlBar>
7084
<div class="flex items-center divide-x h-full">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!--
2+
(c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
<script lang="ts">
8+
import { t } from '../i18n';
9+
import { hasSeenAppVersionRedirectDialog } from '../script/stores/uiStore';
10+
import StandardDialog from './dialogs/StandardDialog.svelte';
11+
import StandardButton from './StandardButton.svelte';
12+
13+
let isOpen = true;
14+
15+
const appVersionRedirect = () => {
16+
hasSeenAppVersionRedirectDialog.set(true);
17+
window.location.href = 'https://ml.microbit.org/v/prototype';
18+
};
19+
20+
const onClose = () => {
21+
hasSeenAppVersionRedirectDialog.set(true);
22+
isOpen = false;
23+
};
24+
</script>
25+
26+
<StandardDialog
27+
{isOpen}
28+
hasCloseButton={false}
29+
closeOnOutsideClick={false}
30+
closeOnEscape={false}
31+
class="w-110 space-y-5"
32+
{onClose}>
33+
<svelte:fragment slot="heading">
34+
{$t('popup.appVersionRedirect.header')}
35+
</svelte:fragment>
36+
<svelte:fragment slot="body">
37+
<div class="space-y-8">
38+
<p>{$t('popup.appVersionRedirect.explain')}</p>
39+
<div class="flex flex-col justify-end space-y-3">
40+
<StandardButton
41+
type="primary"
42+
size="normal"
43+
class="w-sm"
44+
onClick={appVersionRedirect}
45+
>{$t('popup.appVersionRedirect.button.redirect')}</StandardButton>
46+
<StandardButton onClick={onClose} type="secondary" size="normal" class="w-sm"
47+
>{$t('popup.appVersionRedirect.button.stay')}</StandardButton>
48+
</div>
49+
<p class="text-sm">{$t('popup.appVersionRedirect.uk')}</p>
50+
</div>
51+
</svelte:fragment>
52+
</StandardDialog>

src/messages/ui.en.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@
285285
"popup.outdatedmicrobit.button.update": "Update now",
286286
"popup.outdatedmicrobit.button.update.mkcd": "Open MakeCode",
287287

288+
"popup.appVersionRedirect.header": "Are you a UK* primary school teacher?",
289+
"popup.appVersionRedirect.explain": "If you are not a UK* primary school teacher, you will be redirected to our prototype version of this tool. This version is only for the UK's BBC micro:bit playground survey.",
290+
"popup.appVersionRedirect.button.redirect": "I'm not a UK primary school teacher",
291+
"popup.appVersionRedirect.button.stay": "I'm a UK primary school teacher",
292+
"popup.appVersionRedirect.uk": "*includes crown dependencies Jersey, Guernsey and the Isle of Man",
293+
288294
"arrowIconRight.altText": "arrow pointing right",
289295
"arrowIconDown.altText": "arrow pointing down",
290296

src/script/stores/uiStore.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import MBSpecs from '../microbit-interfacing/MBSpecs';
1414
import { gestures } from './Stores';
1515
import { HexOrigin } from '../../StaticConfiguration';
1616
import { DeviceRequestStates } from '../microbit-interfacing/MicrobitConnection';
17+
import { persistantWritable } from './storeUtil';
1718
import { logError, logEvent } from '../utils/logging';
1819

1920
// TODO: Rename? Split up further?
@@ -42,6 +43,11 @@ if (compatibilityResult.bluetooth) {
4243

4344
export const isCompatibilityWarningDialogOpen = writable<boolean>(false);
4445

46+
export const hasSeenAppVersionRedirectDialog = persistantWritable<boolean>(
47+
'hasSeenAppVersionRedirectDialog',
48+
false,
49+
);
50+
4551
export enum ModelView {
4652
TILE,
4753
STACK,

src/script/utils/api.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { logError } from './logging';
2+
3+
/**
4+
* (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*/
8+
interface BrowserInfo {
9+
country?: string;
10+
region?: string;
11+
}
12+
13+
const isBrowserInfo = (v: unknown): v is BrowserInfo => {
14+
return typeof v === 'object' && v !== null;
15+
};
16+
17+
/**
18+
* Best effort attempt to fetch browser info.
19+
* On error it returns empty browser info.
20+
*/
21+
export const fetchBrowserInfo = async (): Promise<BrowserInfo> => {
22+
try {
23+
// Note this API is not available if you're running locally without configuring API_PROXY in .env
24+
const response = await fetch('/api/v1/browser/info');
25+
if (!response.ok) {
26+
return {};
27+
}
28+
const json = await response.json();
29+
if (isBrowserInfo(json)) {
30+
return json;
31+
}
32+
} catch (e) {
33+
logError('Failed to fetch browser info', e);
34+
}
35+
return {};
36+
};

vite.config.ts

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,66 @@
44
*
55
* SPDX-License-Identifier: MIT
66
*/
7-
import { defineConfig } from 'vite';
7+
import { defineConfig, loadEnv } from 'vite';
88
import { svelte } from '@sveltejs/vite-plugin-svelte';
99
import WindiCSS from 'vite-plugin-windicss';
1010
import { preprocessMeltUI, sequence } from '@melt-ui/pp';
1111
import { sveltePreprocess } from 'svelte-preprocess/dist/autoProcess';
1212
import Icons from 'unplugin-icons/vite';
1313

14-
export default defineConfig({
15-
base: process.env.BASE_URL ?? '/',
16-
plugins: [
17-
svelte({
18-
preprocess: sequence([sveltePreprocess({ typescript: true }), preprocessMeltUI()]),
14+
export default defineConfig(({ mode }) => {
15+
const commonEnv = loadEnv(mode, process.cwd(), '');
1916

20-
onwarn(warning, defaultHandler) {
21-
if (warning.code.includes('a11y')) return; // Ignores the a11y warnings when compiling. This does not apply to the editor, see comment at bottom for vscode instructions
17+
return {
18+
base: process.env.BASE_URL ?? '/',
19+
plugins: [
20+
svelte({
21+
preprocess: sequence([
22+
sveltePreprocess({ typescript: true }),
23+
preprocessMeltUI(),
24+
]),
2225

23-
// handle all other warnings normally
24-
defaultHandler!(warning);
26+
onwarn(warning, defaultHandler) {
27+
if (warning.code.includes('a11y')) return; // Ignores the a11y warnings when compiling. This does not apply to the editor, see comment at bottom for vscode instructions
28+
29+
// handle all other warnings normally
30+
defaultHandler!(warning);
31+
},
32+
}),
33+
WindiCSS(),
34+
Icons({ compiler: 'svelte' }),
35+
],
36+
define: {
37+
'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version),
38+
},
39+
build: {
40+
target: 'es2017',
41+
rollupOptions: {
42+
input: 'index.html',
2543
},
26-
}),
27-
WindiCSS(),
28-
Icons({ compiler: 'svelte' }),
29-
],
30-
define: {
31-
'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version),
32-
},
33-
build: {
34-
target: 'es2017',
35-
rollupOptions: {
36-
input: 'index.html',
3744
},
38-
},
39-
test: {
40-
globals: true,
41-
setupFiles: ['./src/setup_tests.ts'],
42-
poolOptions: {
43-
threads: {
44-
// threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982
45-
singleThread: true,
45+
server: commonEnv.API_PROXY
46+
? {
47+
port: 5172,
48+
proxy: {
49+
'/api/v1': {
50+
target: commonEnv.API_PROXY,
51+
changeOrigin: true,
52+
},
53+
},
54+
}
55+
: undefined,
56+
test: {
57+
globals: true,
58+
setupFiles: ['./src/setup_tests.ts'],
59+
poolOptions: {
60+
threads: {
61+
// threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982
62+
singleThread: true,
63+
},
4664
},
4765
},
48-
},
66+
};
4967
});
5068

5169
/**

0 commit comments

Comments
 (0)