diff --git a/.env.example b/.env.example index 927ff37..070c725 100644 --- a/.env.example +++ b/.env.example @@ -13,8 +13,9 @@ # SERVERVAR="foo" # NEXT_PUBLIC_CLIENTVAR="bar" -# Password required to view the site (login page at /login). -# Leave empty/unset to disable the gate. +# Optional password gate for private previews. The gate is off unless both +# values are set, so a stale password alone cannot hide the public site. +SITE_PASSWORD_ENABLED="false" SITE_PASSWORD="" # Umami analytics (optional). Omit NEXT_PUBLIC_UMAMI_WEBSITE_ID to disable tracking. diff --git a/README.md b/README.md index b2c4246..a035f0f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Public routes include: - `/how-it-works` — review loop, medication maintenance, Questions, and handoff - `/privacy` — plain-language privacy and patient-control commitments - `/access` — health-data-free access request +- `/medscribe-corporate-partner-overview.pdf` — public eight-slide partner deck Named partner print material remains isolated and excluded from search: @@ -44,6 +45,19 @@ Set in `.env` (see `.env.example`): Leave the website ID unset to disable tracking (default for local dev). +### Optional preview password + +The public site is open by default. To enable the private-preview gate, set +both values in the deployment environment: + +```bash +SITE_PASSWORD_ENABLED="true" +SITE_PASSWORD="choose-a-strong-password" +``` + +Unset `SITE_PASSWORD_ENABLED` or set it to `false` to keep the site public. A +stored password alone does not enable the gate. + ## Build ```bash @@ -66,11 +80,10 @@ Export **in-app content only** from the simulator or device — no simulator chr 1. Run the app on an **iPhone 17 Pro** simulator or device. 2. Capture each screen (⌘S in Simulator saves to Desktop). -3. Fit each 1206 × 2622 capture into the bezel's 1201 × 2617 display opening, - clip it with a 198 px corner radius, and composite it into the 1350 × 2760 - Silver bezel at offset `+74+71`. Export a transparent, lossless WebP and - update the shared versioned asset directory. - referenced by the routes. +3. Place each 1206 × 2622 capture at native size in the 1350 × 2760 Silver + bezel at offset `+72+69`, clip it to the calibrated display-opening mask, + and export a transparent, lossless WebP into the shared versioned asset + directory referenced by the routes. Product images are composited at the bezel source size of 1350 × 2760 with transparent backgrounds and rendered responsively through diff --git a/public/medscribe-corporate-partner-overview.pdf b/public/medscribe-corporate-partner-overview.pdf new file mode 100644 index 0000000..19f3be2 Binary files /dev/null and b/public/medscribe-corporate-partner-overview.pdf differ diff --git a/src/app/(marketing)/access/page.tsx b/src/app/(marketing)/access/page.tsx index cea4bb5..a1b5e2f 100644 --- a/src/app/(marketing)/access/page.tsx +++ b/src/app/(marketing)/access/page.tsx @@ -19,7 +19,7 @@ import { ACCESS_INVITE_MAILTO } from "~/lib/access"; export const metadata: Metadata = { title: "Request access", description: - "Request access to Medscribe without including personal health information.", + "Request access to Medscribe's closed beta without including personal health information.", }; const fitCards = [ @@ -86,8 +86,9 @@ export default function AccessPage() { Request Medscribe access

- Tell us your platform and whether you are interested as a patient, - caregiver, or both. + The beta is currently closed to a small group. Tell us your + platform and whether you are interested as a patient, caregiver, + or both, and we'll follow up as access becomes available.

+ + + + +

diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index c9fd0bb..5676610 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -8,7 +8,10 @@ import { const COOKIE_MAX_AGE = 60 * 60 * 24 * 30; export async function POST(req: Request) { - const password = process.env.SITE_PASSWORD; + const password = + process.env.SITE_PASSWORD_ENABLED === "true" + ? process.env.SITE_PASSWORD + : undefined; if (!password) { return NextResponse.json({ ok: true }); diff --git a/src/components/site-footer.tsx b/src/components/site-footer.tsx index d8b32a4..4bc91f7 100644 --- a/src/components/site-footer.tsx +++ b/src/components/site-footer.tsx @@ -4,6 +4,10 @@ import { MedscribeLogo } from "~/components/medscribe-logo"; const footerLinks = [ { href: "/how-it-works", label: "How it works" }, + { + href: "/medscribe-corporate-partner-overview.pdf", + label: "Partner deck", + }, { href: "/privacy", label: "Privacy" }, { href: "/access", label: "Request access" }, ] as const; @@ -24,6 +28,9 @@ export function SiteFooter() { A private, patient-controlled way to keep visits, medications, and important care context together.

+

+ Medscribe is a product of Hadlock Holdings LLC. +

Medscribe does not diagnose, prescribe, or replace emergency services or a clinician's verification. @@ -48,20 +55,18 @@ export function SiteFooter() {

Contact

-

- Built by{" "} - - Sean O'Connor - +

+ Sean O'Connor + + Bill Vitriol

- sean@soconnor.dev + access@medscribe.dev

© {new Date().getFullYear()} Hadlock Holdings LLC diff --git a/src/content/product.ts b/src/content/product.ts index a1b66c5..c6a2bdf 100644 --- a/src/content/product.ts +++ b/src/content/product.ts @@ -1,5 +1,5 @@ export const productRelease = { - stage: "Private by design", + stage: "Closed beta", platforms: "iOS and Android", } as const; diff --git a/src/env.js b/src/env.js index 6bd80c8..19502ef 100644 --- a/src/env.js +++ b/src/env.js @@ -9,6 +9,7 @@ export const env = createEnv({ server: { NODE_ENV: z.enum(["development", "test", "production"]), // Optional site-wide password gate via `/login`, enforced in `src/proxy.ts`. + SITE_PASSWORD_ENABLED: z.enum(["true", "false"]).optional(), SITE_PASSWORD: z.string().optional(), }, @@ -30,6 +31,7 @@ export const env = createEnv({ */ runtimeEnv: { NODE_ENV: process.env.NODE_ENV, + SITE_PASSWORD_ENABLED: process.env.SITE_PASSWORD_ENABLED, SITE_PASSWORD: process.env.SITE_PASSWORD, NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL, diff --git a/src/lib/access.ts b/src/lib/access.ts index b9fa069..425094b 100644 --- a/src/lib/access.ts +++ b/src/lib/access.ts @@ -1,2 +1,2 @@ export const ACCESS_INVITE_MAILTO = - "mailto:sean@soconnor.dev?subject=Medscribe%20access&body=Hi%20Sean%2C%0D%0A%0D%0AI%27m%20interested%20in%20Medscribe.%0D%0A%0D%0APlatform%20(iOS%20or%20Android)%3A%0D%0AI%27m%20interested%20as%20a%20(patient%2C%20caregiver%2C%20or%20both)%3A%0D%0A%0D%0APlease%20do%20not%20include%20diagnoses%2C%20medication%20names%2C%20or%20other%20health%20details%20in%20this%20email.%0D%0A%0D%0AThanks%21"; + "mailto:access@medscribe.dev?subject=Medscribe%20access&body=Hi%20Medscribe%20team%2C%0D%0A%0D%0AI%27m%20interested%20in%20Medscribe.%0D%0A%0D%0APlatform%20(iOS%20or%20Android)%3A%0D%0AI%27m%20interested%20as%20a%20(patient%2C%20caregiver%2C%20or%20both)%3A%0D%0A%0D%0APlease%20do%20not%20include%20diagnoses%2C%20medication%20names%2C%20or%20other%20health%20details%20in%20this%20email.%0D%0A%0D%0AThanks%21"; diff --git a/src/proxy.ts b/src/proxy.ts index de4a21c..dd254fb 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -10,17 +10,26 @@ import { /** * Site-wide password gate via a login page and signed session cookie. * - * Set `SITE_PASSWORD` in `.env` to require a password before the site loads. - * Leave `SITE_PASSWORD` unset to disable the gate. + * Set `SITE_PASSWORD_ENABLED=true` and `SITE_PASSWORD` to require a password + * before the site loads. The gate is off by default. */ export async function proxy(req: NextRequest) { - const password = process.env.SITE_PASSWORD; + const password = + process.env.SITE_PASSWORD_ENABLED === "true" + ? process.env.SITE_PASSWORD + : undefined; + const { pathname } = req.nextUrl; - if (!password) return NextResponse.next(); + if (!password) { + if (pathname === "/login") { + return NextResponse.redirect(new URL("/", req.url)); + } + + return NextResponse.next(); + } const token = req.cookies.get(AUTH_COOKIE_NAME)?.value; const isAuthed = await verifyAccessToken(password, token); - const { pathname } = req.nextUrl; if (pathname === "/login" || pathname === "/api/auth") { if (isAuthed && pathname === "/login") {