Add Umami analytics and broaden beta copy for Android.
Optional NEXT_PUBLIC_UMAMI_* env vars load tracking when set; marketing pages now mention iOS TestFlight and Google Play internal testing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,3 +16,7 @@
|
||||
# Password required to view the site (login page at /login).
|
||||
# Leave empty/unset to disable the gate.
|
||||
SITE_PASSWORD=""
|
||||
|
||||
# Umami analytics (optional). Omit NEXT_PUBLIC_UMAMI_WEBSITE_ID to disable tracking.
|
||||
# NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-uuid"
|
||||
# NEXT_PUBLIC_UMAMI_SCRIPT_URL="https://cloud.umami.is/script.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Medscribe Web
|
||||
|
||||
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS.
|
||||
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS and Android.
|
||||
|
||||
Built with [create-t3-app](https://create.t3.gg/) (Next.js + TypeScript + Tailwind). Single-page site; no database or API routes.
|
||||
|
||||
@@ -15,6 +15,15 @@ bun run dev
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
### Analytics (optional)
|
||||
|
||||
Set in `.env` (see `.env.example`):
|
||||
|
||||
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` — website UUID from your Umami dashboard
|
||||
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` — optional; defaults to `https://cloud.umami.is/script.js` (use your self-hosted URL if needed)
|
||||
|
||||
Leave the website ID unset to disable tracking (default for local dev).
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
@@ -26,10 +35,10 @@ Screenshots live in `public/screenshots/`. They are wrapped in an iPhone-style f
|
||||
|
||||
### Capturing raw screenshots for the site
|
||||
|
||||
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app iOS status bar/tab bar is fine.
|
||||
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app status bar/tab bar is fine.
|
||||
|
||||
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device.
|
||||
2. Capture each screen (⌘S in Simulator saves to Desktop).
|
||||
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device, or on an **Android** emulator/device.
|
||||
2. Capture each screen (⌘S in Simulator saves to Desktop; `adb exec-out screencap -p` on Android).
|
||||
3. Drop PNGs into `public/screenshots/` and update paths in `src/app/page.tsx` / `src/components/feature-showcase.tsx`.
|
||||
|
||||
The frame dimensions assume tall iPhone screenshots and are controlled in `src/components/phone-mockup.tsx`. Use `captureMode="simulator"` only for old captures that already include device chrome.
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||
import { PhoneMockup } from "~/components/phone-mockup";
|
||||
import { PrintButton } from "~/components/print-button";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { BETA_FOOTER_NOTE, BETA_SUMMARY } from "~/lib/beta";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import {
|
||||
Card,
|
||||
@@ -326,9 +327,7 @@ export default function ExecutiveSummaryPage() {
|
||||
{/* Beta */}
|
||||
<section className="flex flex-col gap-5 rounded-xl border border-border-soft bg-surface p-6 sm:flex-row sm:items-center sm:justify-between sm:p-8">
|
||||
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground">
|
||||
A beta is available on iOS through TestFlight. Everything runs
|
||||
privately on the device, so no health data leaves the phone. If
|
||||
you'd like to try it, reach out.
|
||||
{BETA_SUMMARY} If you'd like to try it, reach out.
|
||||
</p>
|
||||
<PrintButton size="lg" className="shrink-0" />
|
||||
</section>
|
||||
@@ -459,9 +458,7 @@ export default function ExecutiveSummaryPage() {
|
||||
|
||||
<footer className="mt-auto flex items-center justify-between border-t border-teal-900/10 pt-1.5 text-[10pt] text-slate-500">
|
||||
<p>Sean O'Connor</p>
|
||||
<p className="text-slate-400">
|
||||
A beta is available on iOS through TestFlight.
|
||||
</p>
|
||||
<p className="text-slate-400">{BETA_FOOTER_NOTE}</p>
|
||||
</footer>
|
||||
</article>
|
||||
</>
|
||||
|
||||
@@ -13,6 +13,11 @@ import {
|
||||
import { FeatureShowcase } from "~/components/feature-showcase";
|
||||
import { PhoneMockup } from "~/components/phone-mockup";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import {
|
||||
BETA_CTA_BODY,
|
||||
BETA_CTA_TITLE,
|
||||
BETA_INVITE_MAILTO,
|
||||
} from "~/lib/beta";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
@@ -119,8 +124,9 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-2 flex flex-wrap gap-2">
|
||||
<Badge variant="success">On TestFlight</Badge>
|
||||
<Badge variant="outline">iOS</Badge>
|
||||
<Badge variant="success">Beta available</Badge>
|
||||
<Badge variant="outline">iOS · TestFlight</Badge>
|
||||
<Badge variant="outline">Android · Play internal</Badge>
|
||||
<Badge variant="outline">Emergency-ready</Badge>
|
||||
<Badge variant="outline">No cloud AI</Badge>
|
||||
</div>
|
||||
@@ -319,11 +325,9 @@ export default function HomePage() {
|
||||
<Card className="overflow-hidden border-primary-button/20 bg-primary-button text-white">
|
||||
<CardHeader className="gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="space-y-2">
|
||||
<CardTitle className="text-white">Try Medscribe on iOS</CardTitle>
|
||||
<CardTitle className="text-white">{BETA_CTA_TITLE}</CardTitle>
|
||||
<CardDescription className="text-white/75">
|
||||
The app is live on TestFlight. Reach out for an invite if
|
||||
you're managing medications, visits, or emergency context for
|
||||
someone you love.
|
||||
{BETA_CTA_BODY}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
@@ -332,9 +336,7 @@ export default function HomePage() {
|
||||
variant="outline"
|
||||
className="border-white/30 bg-white text-primary-button hover:bg-white/90"
|
||||
>
|
||||
<a href="mailto:sean@soconnor.dev?subject=Medscribe%20TestFlight%20invite%20request&body=Hi%20Sean%2C%0D%0A%0D%0AI%27d%20like%20a%20TestFlight%20invite%20to%20try%20Medscribe.%20I%27m%20managing%20medications%2C%20visits%2C%20or%20emergency%20context%20for%20someone%20I%20care%20for.%0D%0A%0D%0AThanks%21">
|
||||
Request an invite
|
||||
</a>
|
||||
<a href={BETA_INVITE_MAILTO}>Request an invite</a>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
+6
-1
@@ -3,6 +3,8 @@ import "~/styles/globals.css";
|
||||
import { type Metadata } from "next";
|
||||
import { Instrument_Sans, Playfair_Display } from "next/font/google";
|
||||
|
||||
import { UmamiAnalytics } from "~/components/umami-analytics";
|
||||
|
||||
const instrumentSans = Instrument_Sans({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-instrument-sans",
|
||||
@@ -42,7 +44,10 @@ export default function RootLayout({
|
||||
}: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="en" className={`${instrumentSans.variable} ${playfair.variable}`}>
|
||||
<body className="min-h-screen">{children}</body>
|
||||
<body className="min-h-screen">
|
||||
{children}
|
||||
<UmamiAnalytics />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import Script from "next/script";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
const DEFAULT_UMAMI_SCRIPT_URL = "https://cloud.umami.is/script.js";
|
||||
|
||||
/**
|
||||
* Privacy-friendly page analytics via Umami. Enabled when
|
||||
* `NEXT_PUBLIC_UMAMI_WEBSITE_ID` is set in `.env`.
|
||||
*/
|
||||
export function UmamiAnalytics() {
|
||||
const websiteId = env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
||||
if (!websiteId) return null;
|
||||
|
||||
const src = env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ?? DEFAULT_UMAMI_SCRIPT_URL;
|
||||
|
||||
return (
|
||||
<Script
|
||||
defer
|
||||
src={src}
|
||||
data-website-id={websiteId}
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
);
|
||||
}
|
||||
+6
-2
@@ -18,7 +18,10 @@ export const env = createEnv({
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
/** Umami website ID — omit to disable analytics. */
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.string().optional(),
|
||||
/** Umami script URL (defaults to Umami Cloud). */
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.string().url().optional(),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -28,7 +31,8 @@ export const env = createEnv({
|
||||
runtimeEnv: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
SITE_PASSWORD: process.env.SITE_PASSWORD,
|
||||
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID,
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/** Shared beta / early-access copy for the marketing site. */
|
||||
|
||||
export const BETA_SUMMARY =
|
||||
"A beta is available on iOS (TestFlight) and Android (Google Play internal testing). Everything runs privately on the device, so no health data leaves the phone.";
|
||||
|
||||
export const BETA_CTA_TITLE = "Try Medscribe";
|
||||
|
||||
export const BETA_CTA_BODY =
|
||||
"The app is in beta on iOS and Android. Reach out for an invite if you're managing medications, visits, or emergency context for someone you love.";
|
||||
|
||||
export const BETA_INVITE_MAILTO =
|
||||
"mailto:sean@soconnor.dev?subject=Medscribe%20beta%20invite%20request&body=Hi%20Sean%2C%0D%0A%0D%0AI%27d%20like%20a%20beta%20invite%20to%20try%20Medscribe%20(iOS%20or%20Android).%20I%27m%20managing%20medications%2C%20visits%2C%20or%20emergency%20context%20for%20someone%20I%20care%20for.%0D%0A%0D%0AThanks%21";
|
||||
|
||||
export const BETA_FOOTER_NOTE =
|
||||
"Beta on iOS (TestFlight) and Android (Google Play internal testing).";
|
||||
Reference in New Issue
Block a user