Publish partner deck and open closed beta site
This commit is contained in:
+3
-2
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-4 text-sm leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
<Button asChild size="lg" className="mt-7 w-full sm:w-auto">
|
||||
<a href={ACCESS_INVITE_MAILTO}>
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
ArrowRight,
|
||||
Check,
|
||||
ContactRound,
|
||||
Download,
|
||||
FileText,
|
||||
HeartHandshake,
|
||||
LockKeyhole,
|
||||
ScanLine,
|
||||
@@ -511,6 +513,54 @@ export default function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="partners"
|
||||
className="pitch-lazy overflow-hidden px-4 py-20 sm:px-6 lg:py-28"
|
||||
>
|
||||
<div className="section-wash surface-panel mx-auto grid max-w-6xl gap-10 rounded-[2.75rem] px-6 py-12 sm:px-10 lg:grid-cols-[1fr_auto] lg:items-center lg:px-14 lg:py-16">
|
||||
<div className="pitch-copy-motion max-w-3xl">
|
||||
<div className="bg-primary-light text-primary flex size-12 items-center justify-center rounded-2xl">
|
||||
<FileText className="size-6" aria-hidden="true" />
|
||||
</div>
|
||||
<p className="text-primary mt-7 text-xs font-bold tracking-[0.2em] uppercase">
|
||||
For partners
|
||||
</p>
|
||||
<h2 className="text-foreground mt-4 font-display text-4xl leading-tight font-semibold tracking-[-0.035em] sm:text-5xl">
|
||||
See the partnership story.
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-5 max-w-2xl text-lg leading-relaxed">
|
||||
An eight-slide overview of the problem Medscribe addresses, the
|
||||
product in practice, and the opportunity to bring it to more
|
||||
patients and caregivers.
|
||||
</p>
|
||||
<p className="text-foreground mt-6 text-sm font-semibold tracking-[-0.01em]">
|
||||
<span className="block">Hadlock Holdings LLC</span>
|
||||
<span className="text-muted-foreground mt-1 block">
|
||||
Sean O'Connor · Bill Vitriol
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 sm:flex-row lg:flex-col">
|
||||
<Button asChild size="lg">
|
||||
<a
|
||||
href="/medscribe-corporate-partner-overview.pdf"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View pitch deck
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="outline">
|
||||
<a href="/medscribe-corporate-partner-overview.pdf" download>
|
||||
Download PDF
|
||||
<Download className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="pitch-lazy flex min-h-[82svh] items-center px-4 py-24 sm:px-6 lg:py-32">
|
||||
<div className="pitch-copy-motion section-wash surface-panel mx-auto w-full max-w-6xl overflow-hidden rounded-[3rem] px-6 py-16 text-center sm:px-10 lg:px-20 lg:py-24">
|
||||
<p className="text-primary text-xs font-bold tracking-[0.2em] uppercase">
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-3 text-xs leading-relaxed">
|
||||
Medscribe is a product of Hadlock Holdings LLC.
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-3 text-xs leading-relaxed">
|
||||
Medscribe does not diagnose, prescribe, or replace emergency
|
||||
services or a clinician's verification.
|
||||
@@ -48,20 +55,18 @@ export function SiteFooter() {
|
||||
|
||||
<div>
|
||||
<p className="text-foreground font-semibold">Contact</p>
|
||||
<p className="text-muted-foreground mt-3 leading-relaxed">
|
||||
Built by{" "}
|
||||
<a
|
||||
href="https://soconnor.dev"
|
||||
className="text-foreground focus-visible:ring-primary/40 rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||
>
|
||||
Sean O'Connor
|
||||
</a>
|
||||
<p className="text-foreground mt-3 leading-relaxed">
|
||||
Sean O'Connor
|
||||
<span className="text-muted-foreground mx-2" aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
Bill Vitriol
|
||||
</p>
|
||||
<a
|
||||
href="mailto:sean@soconnor.dev?subject=Medscribe%20website%20question"
|
||||
href="mailto:access@medscribe.dev?subject=Medscribe%20website%20question"
|
||||
className="text-foreground focus-visible:ring-primary/40 mt-2 inline-block rounded-sm underline-offset-4 outline-none hover:underline focus-visible:ring-2"
|
||||
>
|
||||
sean@soconnor.dev
|
||||
access@medscribe.dev
|
||||
</a>
|
||||
<p className="text-muted-foreground/80 mt-5 text-xs">
|
||||
© {new Date().getFullYear()} Hadlock Holdings LLC
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const productRelease = {
|
||||
stage: "Private by design",
|
||||
stage: "Closed beta",
|
||||
platforms: "iOS and Android",
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-1
@@ -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";
|
||||
|
||||
+14
-5
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user