Skip to content

feat(datatrak): DataTrak downloads page#6729

Open
tcaiger wants to merge 9 commits into
devfrom
rn-1549-datatrak-downloads-page
Open

feat(datatrak): DataTrak downloads page#6729
tcaiger wants to merge 9 commits into
devfrom
rn-1549-datatrak-downloads-page

Conversation

@tcaiger

@tcaiger tcaiger commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

RN-1549 DataTrak downloads page

Adds a public /download landing page that walks users through installing DataTrak as a PWA, with platform-appropriate install flows.

Changes:

  • New route /download rendering DownloadPage — a responsive layout with install CTA + help text on mobile, and a QR code + desktop mockup on larger screens. The page is unauthenticated so users can reach it before signing in.
  • User menu entry "Download desktop app" in MenuList, hidden on mobile devices.
  • PWA install plumbing:
    • PwaInstallPromptProvider (hooks/usePwaInstallPrompt.tsx) captures Chromium's beforeinstallprompt event and exposes canPromptInstall / promptInstall() so the mobile install CTA can fire the native install dialog on Android Chrome/Edge. Also listens for appinstalled and infers an initial installed-state from display-mode matchMedia.
    • react-ios-pwa-prompt library (new dep) renders an iOS-styled "Add to Home Screen" overlay for Safari users, triggered manually via isShown. Custom copy passed for title / description / share & add-to-home-screen steps.
    • Click routing in MobileSection: native prompt if available → iOS overlay if on iOS → Android Chrome fallback text otherwise.
  • isIosDevice() helper added to utils/detectDevice.ts alongside the existing isAndroidDevice().
  • New icons: AndroidIcon, AppleIcon SVG components.
  • New assets: desktop/mobile background images and mockup screenshots in public/images/.

Screenshots:


🦸 Review Hero

  • Run Review Hero
  • Auto-fix review suggestions
  • Auto-fix CI failures

@review-hero

review-hero Bot commented Apr 21, 2026

Copy link
Copy Markdown

🦸 Review Hero Summary
6 agents reviewed this PR | 0 critical | 1 suggestion | 0 nitpicks | Filtering: consensus 3 voters, 3 below threshold

Below consensus threshold (3 unique issues not confirmed by majority)
Location Agent Severity Comment
packages/datatrak-web/src/hooks/usePwaInstallPrompt.tsx:36 Bugs & Correctness suggestion isAppInstalled is initialised with useState(isWebApp), which lazily calls isWebApp() once at mount. This works, but the value is never re-checked: if the user installs the app via a mechanism other...
packages/datatrak-web/src/utils/detectDevice.ts:40 Bugs & Correctness critical isIosDevice() uses /iPhone|iPad|iPod/i but iPads running iOS 13+ switched to a desktop user-agent string ('Macintosh, Intel Mac OS X') by default in Safari. This means modern iPads return false her...
packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:234 Bugs & Correctness suggestion When canInstall is false (e.g., on Android in Firefox, or after the beforeinstallprompt event was already dismissed), onClick is set to undefined so the button silently does nothing while still app...
Local fix prompt (copy to your coding agent)

Fix these issues identified on the pull request. One commit per issue fixed.


packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:223: handleInstallClick calls promptInstall() without awaiting it and without disabling the button first. Because promptInstall is async (it awaits deferredPrompt.prompt()), the button remains clickable while the native install dialog is open. A second click before setDeferredPrompt(null) runs will capture the same deferredPrompt from the closure (the state hasn't updated yet) and call .prompt() a second time on an already-consumed BeforeInstallPromptEvent. Fix: set a local 'prompting' state to true before calling and reset it after, or disable the button while the async operation is in flight.

Comment thread packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx Outdated
Comment thread packages/datatrak-web/src/hooks/usePwaInstallPrompt.tsx
Comment thread packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx Outdated
Comment thread packages/datatrak-web/src/hooks/usePwaInstallPrompt.tsx
@review-hero

review-hero Bot commented Apr 21, 2026

Copy link
Copy Markdown

🦸 Review Hero Summary
6 agents reviewed this PR | 0 critical | 3 suggestions | 1 nitpick | Filtering: consensus 3 voters, 5 below threshold

Below consensus threshold (5 unique issues not confirmed by majority)
Location Agent Severity Comment
packages/datatrak-web/src/layout/UserMenu/MenuList.tsx:151 Design & Architecture nitpick The menu item for the download page uses externalIcon but ROUTES.DOWNLOAD is an internal route (/download). Using the external-link icon for internal navigation is a semantic mismatch that wi...
packages/datatrak-web/src/utils/detectDevice.ts:40 Bugs & Correctness suggestion isIosDevice() misses iPadOS 13+. Since iPadOS 13, Safari on iPad sends 'Macintosh' in the user-agent rather than 'iPad', so /iPhone|iPad|iPod/i.test(navigator.userAgent) returns false on modern iPa...
packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:12 Design & Architecture nitpick getDownloadUrl wraps a value that never changes during a session (window.location.origin is stable in a SPA) into a needless function. Define it as a module-level constant instead: `const DOWNL...
packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:148 Design & Architecture suggestion The $visibility prop + visibilityCss helper is threaded through four styled components (RightColumn, Section, MockupImage, and implicitly MobileSection/QRCodeSection) to express a simple show/h...
packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:290 Design & Architecture suggestion MobileSection and QRCodeSection both render on every device and are shown/hidden purely via CSS breakpoints. MobileSection contains live state (showIosPrompt), a PWAPrompt DOM subtree, and calls us...

Nitpicks

File Line Agent Comment
packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx 152 Design & Architecture SectionLabel is an empty styled-component wrapper around Typography with no added styles or semantics. It adds indirection for no benefit — use Typography directly at the call site.
Local fix prompt (copy to your coding agent)

Fix these issues identified on the pull request. One commit per issue fixed.


packages/datatrak-web/src/hooks/usePwaInstallPrompt.tsx:57: If deferredPrompt.prompt() rejects (the spec allows this if the prompt was already consumed or the call is out of context), setDeferredPrompt(null) on line 58 is skipped. The stale deferred prompt stays in state, so canPromptInstall remains true and the install button keeps appearing — but every subsequent click will throw again. Wrap in try/finally: try { const result = await deferredPrompt.prompt(); return result.outcome; } finally { setDeferredPrompt(null); }


packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:222: DownloadButton renders with onClick={undefined} but no disabled prop when canInstall is false (e.g. an Android user on Firefox/Samsung Browser where beforeinstallprompt never fires). The button looks interactive but is completely inert — tapping gives no feedback. At minimum add disabled={!canInstall} so the element is semantically inactive; or hide the button and only show the help text in that case.


packages/datatrak-web/src/views/DownloadPage/DownloadPage.tsx:152: SectionLabel is an empty styled-component wrapper around Typography with no added styles or semantics. It adds indirection for no benefit — use Typography directly at the call site.


packages/datatrak-web/src/hooks/usePwaInstallPrompt.tsx:36: useState(isWebApp) uses isWebApp as a lazy initialiser where isWebApp() returns true when running as a PWA (standalone/fullscreen/minimal-ui). While the logic is correct, isWebApp communicates 'is this running as a non-browser PWA', not 'is the app installed'. The inverted-sounding name will mislead future readers who expect isWebApp to mean 'is this a web browser session'. Consider extracting a clearly-named helper, e.g. isInstalledPwa(), that wraps the same matchMedia checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant