From 5de3ff25b7a5a4624bd0cc3333c39fa58fa94ed6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 18:44:29 +0000 Subject: [PATCH 1/2] Filter app download links by mobile web browser OS The "Download the App" widget now uses the OperatingSystem property from GetDeviceInfo to only show the app store link that integrates with the participant's mobile web browser: iOS shows only the Apple App Store link, Android shows only the Google Play link, and desktop (or an undetermined OS) shows both. The widget continues to only render on the web platform (hidden in the native app). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KogJDCU6R3ovcj4w2VboYr --- .../AppDownload/AppDownload.stories.tsx | 23 ++++++------ .../container/AppDownload/AppDownload.tsx | 36 ++++++++++++++----- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/components/container/AppDownload/AppDownload.stories.tsx b/src/components/container/AppDownload/AppDownload.stories.tsx index 31f3d2e6c..a34b6f9c9 100644 --- a/src/components/container/AppDownload/AppDownload.stories.tsx +++ b/src/components/container/AppDownload/AppDownload.stories.tsx @@ -17,17 +17,20 @@ const Template: StoryFn = (args: AppDownloadProps) => ; -export const WebBoth = Template.bind({}); -WebBoth.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web'}; +export const DesktopBoth = Template.bind({}); +DesktopBoth.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const WebAndroid = Template.bind({}); -WebAndroid.args = {previewProjectPlatforms: ['Web', 'Android'], previewDevicePlatform: 'Web'}; +export const DesktopAndroidOnly = Template.bind({}); +DesktopAndroidOnly.args = {previewProjectPlatforms: ['Web', 'Android'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const WebIOS = Template.bind({}); -WebIOS.args = {previewProjectPlatforms: ['Web', 'iOS'], previewDevicePlatform: 'Web'}; +export const DesktopIOSOnly = Template.bind({}); +DesktopIOSOnly.args = {previewProjectPlatforms: ['Web', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const Android = Template.bind({}); -Android.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Android'}; +export const MobileAndroid = Template.bind({}); +MobileAndroid.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'Android'}; -export const IOS = Template.bind({}); -IOS.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'iOS'}; \ No newline at end of file +export const MobileIOS = Template.bind({}); +MobileIOS.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'iOS'}; + +export const NativeApp = Template.bind({}); +NativeApp.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'iOS', previewOperatingSystem: 'iOS'}; \ No newline at end of file diff --git a/src/components/container/AppDownload/AppDownload.tsx b/src/components/container/AppDownload/AppDownload.tsx index 0ac8ae468..a3aed4982 100644 --- a/src/components/container/AppDownload/AppDownload.tsx +++ b/src/components/container/AppDownload/AppDownload.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState } from 'react' import './AppDownload.css' import MyDataHelps, { ProjectInfo } from '@careevolution/mydatahelps-js' import '@fortawesome/fontawesome-svg-core/styles.css'; @@ -11,6 +11,7 @@ import { CardTitle } from '../../presentational'; export interface AppDownloadProps { previewProjectPlatforms?: string[] previewDevicePlatform?: string; + previewOperatingSystem?: string; innerRef?: React.Ref; title?: string; text?: string; @@ -18,26 +19,43 @@ export interface AppDownloadProps { export default function (props: AppDownloadProps) { const [projectInfo, setProjectInfo] = useState(); + const [operatingSystem, setOperatingSystem] = useState(); useInitializeView(() => { if (props.previewProjectPlatforms) { // @ts-ignore setProjectInfo({ name: 'PROJECT', platforms: props.previewProjectPlatforms || [] }); - return; + } else { + MyDataHelps.getProjectInfo().then(function (projectInfo) { + setProjectInfo(projectInfo); + }); } - MyDataHelps.getProjectInfo().then(function (projectInfo) { - setProjectInfo(projectInfo); - }); + if (props.previewOperatingSystem !== undefined) { + setOperatingSystem(props.previewOperatingSystem); + } else { + MyDataHelps.getDeviceInfo().then(function (deviceInfo) { + setOperatingSystem(deviceInfo?.properties?.OperatingSystem ?? ''); + }).catch(function () { + setOperatingSystem(''); + }); + } }); - if (!projectInfo) { + if (!projectInfo || operatingSystem === undefined) { return null; } let title = props.title || language('app-download-title'); let text = props.text || language('app-download-subtitle').replace("@@PROJECT_NAME@@", projectInfo.name); + // When the participant is on a mobile web browser, only offer the store that + // integrates with their device. On desktop (or when the OS can't be determined) + // show both. This only applies on the web platform; in the native app the whole + // widget is hidden by PlatformSpecificContent below. + let showAndroid = projectInfo.platforms.includes('Android') && operatingSystem !== 'iOS'; + let showIOS = projectInfo.platforms.includes('iOS') && operatingSystem !== 'Android'; + return (
@@ -47,12 +65,12 @@ export default function (props: AppDownloadProps) { {text}
- {projectInfo.platforms.includes('Android') && + {showAndroid && {language('app-download-google-play-link-alt')} } - {projectInfo.platforms.includes('iOS') && + {showIOS && {language('app-download-app-store-link-alt')} @@ -62,4 +80,4 @@ export default function (props: AppDownloadProps) {
); -} \ No newline at end of file +} From 77b4ca1ea78a1e5c08bc864a4c0d4c7b46230320 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 19:00:58 +0000 Subject: [PATCH 2/2] Clarify AppDownload story names Distinguish the two things that filter the store links: the project's configured platforms (DesktopProjectSupports*) versus the mobile web browser's OS (Mobile*Browser), and name the hidden native-app case explicitly. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KogJDCU6R3ovcj4w2VboYr --- .../AppDownload/AppDownload.stories.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/container/AppDownload/AppDownload.stories.tsx b/src/components/container/AppDownload/AppDownload.stories.tsx index a34b6f9c9..6b5d65baf 100644 --- a/src/components/container/AppDownload/AppDownload.stories.tsx +++ b/src/components/container/AppDownload/AppDownload.stories.tsx @@ -17,20 +17,25 @@ const Template: StoryFn = (args: AppDownloadProps) => ; -export const DesktopBoth = Template.bind({}); -DesktopBoth.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; +// On a desktop web browser both store links are shown, limited only by which +// platforms the project itself supports. +export const DesktopProjectSupportsAllPlatforms = Template.bind({}); +DesktopProjectSupportsAllPlatforms.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const DesktopAndroidOnly = Template.bind({}); -DesktopAndroidOnly.args = {previewProjectPlatforms: ['Web', 'Android'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; +export const DesktopProjectSupportsAndroidOnly = Template.bind({}); +DesktopProjectSupportsAndroidOnly.args = {previewProjectPlatforms: ['Web', 'Android'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const DesktopIOSOnly = Template.bind({}); -DesktopIOSOnly.args = {previewProjectPlatforms: ['Web', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; +export const DesktopProjectSupportsIOSOnly = Template.bind({}); +DesktopProjectSupportsIOSOnly.args = {previewProjectPlatforms: ['Web', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: ''}; -export const MobileAndroid = Template.bind({}); -MobileAndroid.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'Android'}; +// On a mobile web browser only the store link matching the device's OS is shown, +// even when the project supports all platforms. +export const MobileAndroidBrowser = Template.bind({}); +MobileAndroidBrowser.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'Android'}; -export const MobileIOS = Template.bind({}); -MobileIOS.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'iOS'}; +export const MobileIPhoneBrowser = Template.bind({}); +MobileIPhoneBrowser.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'Web', previewOperatingSystem: 'iOS'}; -export const NativeApp = Template.bind({}); -NativeApp.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'iOS', previewOperatingSystem: 'iOS'}; \ No newline at end of file +// In the native app the widget is hidden entirely (device platform is not Web). +export const NativeAppHidden = Template.bind({}); +NativeAppHidden.args = {previewProjectPlatforms: ['Web', 'Android', 'iOS'], previewDevicePlatform: 'iOS', previewOperatingSystem: 'iOS'}; \ No newline at end of file