mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(web): ask Safari users for Mac type (#2090)
Signed-off-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79 <1a7496365ae2df8adb1b932a1a6f68b9cb3950f3d179c8a451c32d194e8f7dc1@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n
npub1rf6fvdj6ut0c4kcmjv4p5mmgh89nj58n69uu3fz3cvk3jn500hqs7emz79
parent
cab47905a8
commit
820d023305
@@ -1,7 +1,9 @@
|
||||
import buzzAppIcon from "@/assets/app-icon@3x.png";
|
||||
import {
|
||||
BUZZ_RELEASES_URL,
|
||||
resolveBuzzDownloadUrl,
|
||||
type BuzzDownloadPlatform,
|
||||
detectBuzzDownloadPlatform,
|
||||
resolveBuzzDownloadUrlForPlatform,
|
||||
} from "@/shared/lib/buzz-download";
|
||||
import { relayWsUrl } from "@/shared/lib/relay-url";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
@@ -32,9 +34,29 @@ export function InvitePage({ code }: { code: string }) {
|
||||
const [agreementConfirmed, setAgreementConfirmed] = React.useState(false);
|
||||
const [opening, setOpening] = React.useState(false);
|
||||
const [downloadUrl, setDownloadUrl] = React.useState(BUZZ_RELEASES_URL);
|
||||
const [needsMacChoice, setNeedsMacChoice] = React.useState(false);
|
||||
const [showMacChoice, setShowMacChoice] = React.useState(false);
|
||||
const [choosingMacDownload, setChoosingMacDownload] = React.useState(false);
|
||||
const choosingMacDownloadRef = React.useRef(false);
|
||||
const downloadTriggerRef = React.useRef<HTMLAnchorElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
resolveBuzzDownloadUrl().then(setDownloadUrl);
|
||||
let active = true;
|
||||
detectBuzzDownloadPlatform(navigator).then(async (platform) => {
|
||||
if (!active) return;
|
||||
if (
|
||||
platform.operatingSystem === "macos" &&
|
||||
platform.architecture === "unknown"
|
||||
) {
|
||||
setNeedsMacChoice(true);
|
||||
return;
|
||||
}
|
||||
const url = await resolveBuzzDownloadUrlForPlatform(platform);
|
||||
if (active) setDownloadUrl(url);
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -89,6 +111,38 @@ export function InvitePage({ code }: { code: string }) {
|
||||
);
|
||||
const showDocument = (title: string, markdown: string) =>
|
||||
setDocument({ title, markdown });
|
||||
const closeMacChoice = React.useCallback(() => {
|
||||
setShowMacChoice(false);
|
||||
window.setTimeout(() => downloadTriggerRef.current?.focus());
|
||||
}, []);
|
||||
const chooseMacDownload = async (
|
||||
event: React.MouseEvent<HTMLAnchorElement>,
|
||||
platform: BuzzDownloadPlatform,
|
||||
) => {
|
||||
event.preventDefault();
|
||||
if (choosingMacDownloadRef.current) return;
|
||||
choosingMacDownloadRef.current = true;
|
||||
setChoosingMacDownload(true);
|
||||
const downloadWindow = window.open("about:blank", "_blank");
|
||||
if (downloadWindow) downloadWindow.opener = null;
|
||||
setShowMacChoice(false);
|
||||
try {
|
||||
const url = await resolveBuzzDownloadUrlForPlatform(platform);
|
||||
downloadWindow?.location.replace(url);
|
||||
} finally {
|
||||
choosingMacDownloadRef.current = false;
|
||||
setChoosingMacDownload(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!showMacChoice) return;
|
||||
const closeOnEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") closeMacChoice();
|
||||
};
|
||||
window.document.addEventListener("keydown", closeOnEscape);
|
||||
return () => window.document.removeEventListener("keydown", closeOnEscape);
|
||||
}, [closeMacChoice, showMacChoice]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -157,16 +211,96 @@ export function InvitePage({ code }: { code: string }) {
|
||||
<p className="flex h-[3.125rem] items-center justify-center rounded-2xl bg-white text-sm text-black/60">
|
||||
Don't have the app?{" "}
|
||||
<a
|
||||
aria-expanded={needsMacChoice ? showMacChoice : undefined}
|
||||
aria-haspopup={needsMacChoice ? "dialog" : undefined}
|
||||
className="ml-1 font-medium text-black underline-offset-4 hover:text-black/70 hover:underline focus-visible:underline"
|
||||
href={downloadUrl}
|
||||
ref={downloadTriggerRef}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
onClick={(event) => {
|
||||
if (!needsMacChoice) return;
|
||||
event.preventDefault();
|
||||
setShowMacChoice(true);
|
||||
}}
|
||||
>
|
||||
Download it now
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{showMacChoice && (
|
||||
<div
|
||||
aria-label="Which Mac do you have?"
|
||||
aria-modal="true"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 text-left"
|
||||
role="dialog"
|
||||
onMouseDown={(event) => {
|
||||
if (event.currentTarget === event.target) closeMacChoice();
|
||||
}}
|
||||
>
|
||||
<div className="w-full max-w-lg rounded-3xl bg-white p-7 text-black shadow-xl sm:p-9">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Which Mac do you have?
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-black/60">
|
||||
Choose based on when your Mac was released.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="text-2xl leading-none text-black/60 hover:text-black"
|
||||
type="button"
|
||||
onClick={closeMacChoice}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-6 grid gap-3">
|
||||
<a
|
||||
aria-disabled={choosingMacDownload}
|
||||
className="rounded-2xl border border-black p-5 text-black no-underline hover:bg-black hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black aria-disabled:pointer-events-none aria-disabled:opacity-50"
|
||||
href={BUZZ_RELEASES_URL}
|
||||
onClick={(event) =>
|
||||
void chooseMacDownload(event, {
|
||||
operatingSystem: "macos",
|
||||
architecture: "arm64",
|
||||
})
|
||||
}
|
||||
>
|
||||
<strong className="block text-lg">Newer Mac</strong>
|
||||
<span className="mt-1 block text-sm">
|
||||
2021 or later, or a late-2020 Mac with an Apple M1 chip
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
aria-disabled={choosingMacDownload}
|
||||
className="rounded-2xl border border-black p-5 text-black no-underline hover:bg-black hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-black aria-disabled:pointer-events-none aria-disabled:opacity-50"
|
||||
href={BUZZ_RELEASES_URL}
|
||||
onClick={(event) =>
|
||||
void chooseMacDownload(event, {
|
||||
operatingSystem: "macos",
|
||||
architecture: "x64",
|
||||
})
|
||||
}
|
||||
>
|
||||
<strong className="block text-lg">Older Mac</strong>
|
||||
<span className="mt-1 block text-sm">
|
||||
2019 or earlier, or a 2020 Mac with an Intel processor
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<p className="mt-5 text-sm leading-5">
|
||||
<strong>Not sure?</strong> Open the Apple menu and choose{" "}
|
||||
<strong>About This Mac</strong>. “Chip: Apple M…” means Newer Mac.
|
||||
“Processor: Intel” means Older Mac.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{document && (
|
||||
<div
|
||||
aria-label={document.title}
|
||||
|
||||
@@ -110,11 +110,9 @@ export async function detectBuzzDownloadPlatform(
|
||||
function assetPattern(platform: BuzzDownloadPlatform): RegExp | undefined {
|
||||
switch (platform.operatingSystem) {
|
||||
case "macos":
|
||||
// Safari withholds CPU architecture and reports MacIntel on Apple
|
||||
// Silicon. The Intel build remains compatible there through Rosetta.
|
||||
return platform.architecture === "arm64"
|
||||
? /_aarch64\.dmg$/i
|
||||
: /_x64\.dmg$/i;
|
||||
if (platform.architecture === "arm64") return /_aarch64\.dmg$/i;
|
||||
if (platform.architecture === "x64") return /_x64\.dmg$/i;
|
||||
return undefined;
|
||||
case "windows":
|
||||
return /_x64-setup[^/]*\.exe$/i;
|
||||
case "linux":
|
||||
@@ -141,8 +139,9 @@ export function selectBuzzDownloadUrl(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function resolveBuzzDownloadUrl(): Promise<string> {
|
||||
const platform = await detectBuzzDownloadPlatform(navigator);
|
||||
export async function resolveBuzzDownloadUrlForPlatform(
|
||||
platform: BuzzDownloadPlatform,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const cached = JSON.parse(sessionStorage.getItem(CACHE_KEY) ?? "null") as {
|
||||
expiresAt: number;
|
||||
@@ -188,3 +187,9 @@ export async function resolveBuzzDownloadUrl(): Promise<string> {
|
||||
return BUZZ_RELEASES_URL;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveBuzzDownloadUrl(): Promise<string> {
|
||||
return resolveBuzzDownloadUrlForPlatform(
|
||||
await detectBuzzDownloadPlatform(navigator),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,65 @@ test("invite requires age and legal consent before opening Buzz", async ({
|
||||
expect(consentBox?.width).toBe(acceptButtonBox?.width);
|
||||
});
|
||||
|
||||
test("invite asks Safari users to choose their Mac download", async ({
|
||||
browser,
|
||||
}) => {
|
||||
const context = await browser.newContext({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Version/26.5 Safari/605.1.15",
|
||||
});
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperties(navigator, {
|
||||
platform: { configurable: true, value: "MacIntel" },
|
||||
maxTouchPoints: { configurable: true, value: 0 },
|
||||
userAgentData: { configurable: true, value: undefined },
|
||||
});
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.route("**/api/join-policy", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ policy: null }),
|
||||
});
|
||||
});
|
||||
await page.route("https://api.github.com/**", async (route) => {
|
||||
await route.fulfill({ status: 500 });
|
||||
});
|
||||
|
||||
await page.goto("/invite/demo-code");
|
||||
const download = page.getByRole("link", { name: "Download it now" });
|
||||
await expect(download).toHaveAttribute("aria-haspopup", "dialog");
|
||||
await download.click();
|
||||
|
||||
const chooser = page.getByRole("dialog", {
|
||||
name: "Which Mac do you have?",
|
||||
});
|
||||
await expect(chooser).toBeVisible();
|
||||
await expect(chooser.getByRole("link", { name: /Newer Mac/ })).toContainText(
|
||||
"2021 or later, or a late-2020 Mac with an Apple M1 chip",
|
||||
);
|
||||
await expect(chooser.getByRole("link", { name: /Older Mac/ })).toContainText(
|
||||
"2019 or earlier, or a 2020 Mac with an Intel processor",
|
||||
);
|
||||
await expect(chooser.getByText("About This Mac")).toBeVisible();
|
||||
|
||||
const openedPagePromise = context.waitForEvent("page");
|
||||
await chooser.getByRole("link", { name: /Newer Mac/ }).click();
|
||||
const openedPage = await openedPagePromise;
|
||||
await expect(chooser).toBeHidden();
|
||||
await expect(openedPage).toHaveURL("https://github.com/block/buzz/releases");
|
||||
await expect(page).toHaveURL(/\/invite\/demo-code$/);
|
||||
await openedPage.close();
|
||||
|
||||
await download.click();
|
||||
await expect(chooser).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(chooser).toBeHidden();
|
||||
await expect(download).toBeFocused();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test("invite download falls back for mobile and non-desktop devices", async ({
|
||||
browser,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user