Merge pull request #51 from BharathxD/refactor/download-page

refactor: Improved platform detection, UI enhancements, and overall code quality
This commit is contained in:
mauro 🤙
2024-08-25 00:48:44 +02:00
committed by GitHub
3 changed files with 293 additions and 314 deletions

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { addDownload } from "@/lib/db";
import { useState, useEffect } from "react"; import { incrementDownloadCount } from "@/lib/db";
import { useState, useEffect, useCallback } from "react";
import styled, { keyframes } from "styled-components"; import styled, { keyframes } from "styled-components";
import { ny } from "@/lib/utils"; import { ny } from "@/lib/utils";
import { Checkbox } from "./ui/checkbox"; import { Checkbox } from "./ui/checkbox";
@@ -8,50 +9,36 @@ import { ChevronLeft } from "lucide-react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import Particles from "./ui/particles"; import Particles from "./ui/particles";
import confetti from "canvas-confetti"; import confetti from "canvas-confetti";
import type {
Architecture,
LinuxDownloadType,
Platform,
WindowsDownloadType,
} from "@/lib/releases";
import { releases, releaseTree } from "@/lib/releases"; import { releases, releaseTree } from "@/lib/releases";
import { InfoCircledIcon } from "@radix-ui/react-icons"; import { InfoCircledIcon } from "@radix-ui/react-icons";
import Link from "next/link"; import Link from "next/link";
import SparklesText from "./ui/sparkles-text";
import { useRouter } from "next/navigation";
const BASE_URL = const BASE_URL =
"https://github.com/zen-browser/desktop/releases/latest/download"; "https://github.com/zen-browser/desktop/releases/latest/download";
import SparklesText from "./ui/sparkles-text";
const field_enter = keyframes` const field_enter = keyframes`
0% { 0% { opacity: 0; transform: scale(0.9); filter: blur(10px); }
opacity: 0; 1% { max-height: 100%; }
transform: scale(0.9); 100% { opacity: 1; transform: scale(1); filter: blur(0); }
filter: blur(10px);
}
1% {
max-height: 100%;
}
100% {
opacity: 1;
transform: scale(1);
filter: blur(0);
}
`; `;
const field_exit = keyframes` const field_exit = keyframes`
from { from { display: flex; opacity: 1; transform: scale(1); filter: blur(0); }
display: flex; 99% { opacity: 0; transform: scale(0.9); filter: blur(10px); }
opacity: 1; 100% { display: none; }
transform: scale(1);
filter: blur(0);
}
99% {
opacity: 0;
transform: scale(0.9);
filter: blur(10px);
}
100% {
display: none;
}
`; `;
const FormField = styled.div<{ enter: boolean; out: boolean }>` const FormField = styled.div<{ enter: boolean; out: boolean }>`
max-height: 0; max-height: 0;
flex-direction: column; flex-direction: column;
margin-top: 3rem;
opacity: 0; opacity: 0;
width: 100%; width: 100%;
animation: 0.2s ease-in-out forwards animation: 0.2s ease-in-out forwards
@@ -59,53 +46,59 @@ const FormField = styled.div<{ enter: boolean; out: boolean }>`
animation-delay: ${({ enter }) => (enter ? "0.4s" : "0s")}; animation-delay: ${({ enter }) => (enter ? "0.4s" : "0s")};
`; `;
const FieldTitle = styled.div` const FieldTitle = ({
font-size: 1.35rem; children,
font-weight: 500; className,
`; }: React.PropsWithChildren & React.HTMLAttributes<HTMLHeadElement>) => (
<h2 className={ny("text-xl font-medium", className)}>{children}</h2>
);
const FieldDescription = styled.div` const FieldDescription = ({
font-size: 1rem; children,
color: #666; className,
margin-bottom: 1rem; }: React.PropsWithChildren & React.HTMLAttributes<HTMLDivElement>) => (
`; <p className={ny("text-base text-[#666] mb-4", className)}>{children}</p>
);
const platforms = {
Windows: { color: "blue", icon: "windows8" },
Linux: { color: "yellow", icon: "linux" },
MacOS: { color: "purple", icon: "apple" },
};
export default function DownloadPage() { export default function DownloadPage() {
const [platform, setPlatform] = useState<string | null>(null); const router = useRouter();
const [architecture, setArchitecture] = useState<string | null>(null);
const [windowsDownloadType, setWindowsDownloadType] = useState<string | null>( const [platform, setPlatform] = useState<Platform | null>(null);
null const [selectedPlatform, setSelectedPlatform] = useState<Platform | null>(
);
const [linuxDownloadType, setLinuxDownloadType] = useState<string | null>(
null null
); );
const [selectedPlatform, setSelectedPlatform] = useState(""); const [selectedArchitecture, setSelectedArchitecture] =
const [selectedArchitecture, setSelectedArchitecture] = useState("specific"); useState<Architecture>("specific");
const [selectedWindowsDownloadType, setSelectedWindowsDownloadType] = const [selectedWindowsDownloadType, setSelectedWindowsDownloadType] =
useState("installer"); useState<WindowsDownloadType>("installer");
const [selectedLinuxDownloadType, setSelectedLinuxDownloadType] = const [selectedLinuxDownloadType, setSelectedLinuxDownloadType] =
useState("portable"); useState<LinuxDownloadType>("portable");
const [hasDownloaded, setHasDownloaded] = useState(false); const [hasDownloaded, setHasDownloaded] = useState<boolean>(false);
const [flowIndex, setFlowIndex] = useState<number>(0);
const [flowIndex, setFlowIndex] = useState(0); const detectPlatform = useCallback(() => {
useEffect(() => { if (typeof window === "undefined") return;
let userAgent: string = "";
if (typeof window !== "undefined") { const userAgent = window.navigator.userAgent.toLowerCase();
userAgent = window.navigator.userAgent;
} if (userAgent.includes("win")) {
if (userAgent.includes("Win")) {
setSelectedPlatform("Windows"); setSelectedPlatform("Windows");
} } else if (userAgent.includes("mac")) {
if (userAgent.includes("Mac")) {
setSelectedPlatform("MacOS"); setSelectedPlatform("MacOS");
} } else if (userAgent.includes("linux")) {
if (userAgent.includes("Linux")) {
setSelectedPlatform("Linux"); setSelectedPlatform("Linux");
} }
}, []); }, []);
const throwConfetti = () => {
const throwConfetti = useCallback(() => {
const end = Date.now() + 3 * 1000; // 3 seconds const end = Date.now() + 3 * 1000; // 3 seconds
const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"]; const colors = ["#a786ff", "#fd8bbc", "#eca184", "#f8deb1"];
const frame = () => { const frame = () => {
@@ -130,68 +123,75 @@ export default function DownloadPage() {
requestAnimationFrame(frame); requestAnimationFrame(frame);
}; };
frame(); frame();
}; }, []);
const startDownload = () => { const getReleaseTarget = useCallback(():
let releaseTarget: string; | keyof typeof releases
| undefined => {
if (selectedLinuxDownloadType === "flatpak") { if (selectedLinuxDownloadType === "flatpak") {
window.open( router.push(
"https://dl.flathub.org/repo/appstream/io.github.zen_browser.zen.flatpakref" "https://dl.flathub.org/repo/appstream/io.github.zen_browser.zen.flatpakref"
); );
releaseTarget = "flatpak"; return;
} else {
const platform = releaseTree[selectedPlatform.toLowerCase()];
let arch: string = selectedArchitecture;
if (selectedPlatform === "MacOS") {
releaseTarget = platform[arch];
} else {
releaseTarget =
platform[arch][
selectedPlatform === "Windows"
? (selectedWindowsDownloadType as string)
: (selectedLinuxDownloadType as string)
];
}
console.log("Downloading: ");
console.log("platform: ", selectedPlatform);
console.log("compat: ", arch);
window.location.replace(`${BASE_URL}/${releases[releaseTarget]}`);
} }
setHasDownloaded(true);
addDownload(releaseTarget);
throwConfetti();
};
const continueFlow = () => { if (!selectedPlatform) return;
const platformReleases = releaseTree[selectedPlatform.toLowerCase()];
if (selectedPlatform === "MacOS")
return platformReleases[selectedArchitecture];
return platformReleases[selectedArchitecture][
selectedPlatform === "Windows"
? selectedWindowsDownloadType
: selectedLinuxDownloadType
];
}, [
router,
selectedArchitecture,
selectedLinuxDownloadType,
selectedPlatform,
selectedWindowsDownloadType,
]);
const startDownload = useCallback(() => {
const releaseTarget = getReleaseTarget();
if (!releaseTarget) return;
router.push(`${BASE_URL}/${releases[releaseTarget]}`);
setHasDownloaded(true);
incrementDownloadCount(releaseTarget);
throwConfetti();
}, [getReleaseTarget, router, throwConfetti]);
const continueFlow = useCallback(() => {
if (flowIndex === 0) setPlatform(selectedPlatform); if (flowIndex === 0) setPlatform(selectedPlatform);
if (flowIndex === 1) setArchitecture(selectedArchitecture);
if (flowIndex === 2 || (flowIndex === 1 && platform === "MacOS")) { if (flowIndex === 2 || (flowIndex === 1 && platform === "MacOS")) {
setWindowsDownloadType(selectedWindowsDownloadType);
setLinuxDownloadType(selectedLinuxDownloadType);
startDownload(); startDownload();
} }
setFlowIndex(flowIndex + 1); setFlowIndex(flowIndex + 1);
}; }, [flowIndex, platform, selectedPlatform, startDownload]);
const goBackFlow = () => { const goBackFlow = useCallback(() => {
if (flowIndex === 1) { if (flowIndex === 1) setPlatform(null);
setPlatform(null); else if (flowIndex === 3) {
} else if (flowIndex === 2) {
setArchitecture(null);
} else if (flowIndex === 3) {
setWindowsDownloadType(null);
setSelectedWindowsDownloadType("installer"); setSelectedWindowsDownloadType("installer");
setLinuxDownloadType(null);
setSelectedLinuxDownloadType("portable"); setSelectedLinuxDownloadType("portable");
} }
if (flowIndex > 0) setFlowIndex(flowIndex - 1); if (flowIndex > 0) setFlowIndex(flowIndex - 1);
}; }, [flowIndex]);
const changeToFlatpak = () => { const changeToFlatpak = useCallback(() => {
if (selectedArchitecture === "specific") { if (selectedArchitecture !== "specific") return;
setSelectedLinuxDownloadType("flatpak"); setSelectedLinuxDownloadType("flatpak");
} }, [selectedArchitecture]);
};
useEffect(() => {
detectPlatform();
}, [detectPlatform]);
return ( return (
<> <>
@@ -244,63 +244,52 @@ export default function DownloadPage() {
<Button <Button
className="mt-5" className="mt-5"
onClick={() => onClick={() =>
(window.location.href = router.push(
"https://docs.zen-browser.app/guides/install-macos") "https://docs.zen-browser.app/guides/install-macos"
)
} }
> >
Download Zen for MacOS Read Installation Instructions
</Button> </Button>
</div> </div>
)} )}
</div> </div>
)) || ( )) || (
<> <>
<h1 className="text-6xl font-bold flex flex-col lg:flex-row">Download <SparklesText className="mx-2" text="Zen" /></h1> <h1 className="text-6xl font-bold flex flex-col lg:flex-row">
Download <SparklesText className="mx-2" text="Zen" />
</h1>
<p className="text-muted-foreground mt-3"> <p className="text-muted-foreground mt-3">
We're thrilled for you to experience Zen Browser. First, let us know which device you're using. This will only take a moment, we promise. We're thrilled for you to experience Zen Browser. First, let us
know which device you're using. This will only take a moment, we
promise.
</p> </p>
</> </>
)} )}
<div className="relative w-full"> <div className="relative w-full pt-12">
{platform === null && ( {/* Platform */}
<FormField enter={platform === null} out={platform !== null}> {!platform && (
<FormField enter={!platform} out={!platform}>
<FieldTitle>Platform</FieldTitle> <FieldTitle>Platform</FieldTitle>
<FieldDescription> <FieldDescription>
Choose the platform you want to download Zen for. Choose the platform you want to download Zen for.
</FieldDescription> </FieldDescription>
<div {Object.entries(platforms).map(([plat, { color, icon }]) => (
onClick={() => setSelectedPlatform("Windows")} <div
className={ny( key={plat}
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border", onClick={() => setSelectedPlatform(plat as Platform)}
selectedPlatform === "Windows" ? "border-blue-400" : "" className={ny(
)} "select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border",
> selectedPlatform === plat && `border-${color}-400`
<Checkbox checked={selectedPlatform === "Windows"} /> )}
<i className="devicon-windows8-original ml-3 p-2 border border-blue-400 rounded-lg"></i> >
<div className="ml-2">Windows</div> <Checkbox checked={selectedPlatform === plat} />
</div> <i
<div className={`devicon-${icon}-plain ml-3 p-2 border border-${color}-400 rounded-lg`}
onClick={() => setSelectedPlatform("Linux")} ></i>
className={ny( <div className="ml-2">{plat}</div>
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border", </div>
selectedPlatform === "Linux" ? "border-yellow-400" : "" ))}
)}
>
<Checkbox checked={selectedPlatform === "Linux"} />
<i className="devicon-linux-plain ml-3 p-2 border border-yellow-400 rounded-lg"></i>
<div className="ml-2">Linux</div>
</div>
<div
onClick={() => setSelectedPlatform("MacOS")}
className={ny(
"select-none mb-2 px-4 py-3 flex items-center rounded-lg bg-background cursor-pointer border",
selectedPlatform === "MacOS" ? "border-purple-400" : ""
)}
>
<Checkbox checked={selectedPlatform === "MacOS"} />
<i className="devicon-apple-original p-2 border border-purple-400 ml-3 rounded-lg"></i>
<div className="ml-2 font-bold">MacOS</div>
</div>
</FormField> </FormField>
)} )}
{/* Architecture */} {/* Architecture */}
@@ -322,38 +311,30 @@ export default function DownloadPage() {
Choose the architecture of your device, either optimized or Choose the architecture of your device, either optimized or
generic. generic.
</FieldDescription> </FieldDescription>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center gap-2">
<div <div
onClick={() => setSelectedArchitecture("specific")} onClick={() => setSelectedArchitecture("specific")}
className={ny( className={ny(
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedArchitecture === "specific" selectedArchitecture === "specific" && "border-blue-400"
? "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl my-2">🚀</h1>
🚀 <h1 className="text-xl font-semibold my-2">Optimized</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Optimized</h1>
<p className="text-muted-foreground mx-auto text-center">
Blazing fast and compatible with modern devices Blazing fast and compatible with modern devices
</p> </p>
</div> </div>
<div <div
onClick={() => setSelectedArchitecture("generic")} onClick={() => setSelectedArchitecture("generic")}
className={ny( className={ny(
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedArchitecture === "generic" selectedArchitecture === "generic" && "border-blue-400"
? "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl my-2">👴</h1>
👴 <h1 className="text-xl font-semibold my-2">Generic</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Generic</h1>
<p className="text-muted-foreground mx-auto text-center">
Slow but compatible with older devices. Slow but compatible with older devices.
</p> </p>
</div> </div>
@@ -369,39 +350,33 @@ export default function DownloadPage() {
<FieldDescription> <FieldDescription>
Click the button below to download Zen for MacOS. Click the button below to download Zen for MacOS.
</FieldDescription> </FieldDescription>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center gap-2">
<div <button
onClick={() => setSelectedArchitecture("specific")} onClick={() => setSelectedArchitecture("specific")}
className={ny( className={ny(
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedArchitecture === "specific" selectedArchitecture === "specific" && "border-blue-400"
? "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl my-2">🍏</h1>
🍏 <h1 className="text-xl font-semibold my-2">aarch64</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">aarch64</h1> 64-bit ARM architecture, for Apple's M Series Chips
<p className="text-muted-foreground mx-auto text-center">64-bit ARM architecture, for Apple's M Series Chips</p> </p>
</div> </button>
<div <button
onClick={() => setSelectedArchitecture("generic")} onClick={() => setSelectedArchitecture("generic")}
className={ny( className={ny(
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedArchitecture === "generic" selectedArchitecture === "generic" && "border-blue-400"
? "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl font-bold my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl font-bold my-2">x64</h1>
x64 <h1 className="text-xl font-semibold my-2">Intel</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Intel</h1>
<p className="text-muted-foreground mx-auto text-center">
64-bit Intel architecture, for older Macs 64-bit Intel architecture, for older Macs
</p> </p>
</div> </button>
</div> </div>
</FormField> </FormField>
)} )}
@@ -416,41 +391,35 @@ export default function DownloadPage() {
<FieldDescription> <FieldDescription>
Choose the type of download you want for Zen for Windows. Choose the type of download you want for Zen for Windows.
</FieldDescription> </FieldDescription>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center gap-2">
<div <button
onClick={() => setSelectedWindowsDownloadType("installer")} onClick={() => setSelectedWindowsDownloadType("installer")}
className={ny( className={ny(
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedWindowsDownloadType === "installer" selectedWindowsDownloadType === "installer" &&
? "border-blue-400" "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl my-2">🚀</h1>
🚀 <h1 className="text-xl font-semibold my-2">Installer</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Installer</h1>
<p className="text-muted-foreground mx-auto text-center">
Install Zen with a setup wizard Install Zen with a setup wizard
</p> </p>
</div> </button>
<div <button
onClick={() => setSelectedWindowsDownloadType("portable")} onClick={() => setSelectedWindowsDownloadType("portable")}
className={ny( className={ny(
"select-none w-full h-full mb-2 ml-10 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border",
selectedWindowsDownloadType === "portable" selectedWindowsDownloadType === "portable" &&
? "border-blue-400" "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl my-2">📦</h1>
📦 <h1 className="text-xl font-semibold my-2">Portable</h1>
</h1> <p className="text-muted-foreground mx-auto text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Portable</h1>
<p className="text-muted-foreground mx-auto text-center">
Download Zen as a ZIP file Download Zen as a ZIP file
</p> </p>
</div> </button>
</div> </div>
</FormField> </FormField>
)} )}
@@ -465,61 +434,51 @@ export default function DownloadPage() {
<FieldDescription> <FieldDescription>
Choose the type of download you want for Zen for Linux. Choose the type of download you want for Zen for Linux.
</FieldDescription> </FieldDescription>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center gap-2">
<div <article
onClick={() => setSelectedLinuxDownloadType("appimage")} onClick={() => setSelectedLinuxDownloadType("appimage")}
className={ny( className={ny(
"select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border space-y-2",
selectedLinuxDownloadType === "appimage" selectedLinuxDownloadType === "appimage" &&
? "border-blue-400" "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl">🚀</h1>
🚀 <h1 className="text-xl font-semibold">AppImage</h1>
</h1> <p className="text-muted-foreground text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">AppImage</h1>
<p className="text-muted-foreground mx-auto text-center">
Install Zen with a setup wizard Install Zen with a setup wizard
</p> </p>
</div> </article>
<div <article
onClick={() => setSelectedLinuxDownloadType("portable")} onClick={() => setSelectedLinuxDownloadType("portable")}
className={ny( className={ny(
"select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border space-y-2",
selectedLinuxDownloadType === "portable" selectedLinuxDownloadType === "portable" &&
? "border-blue-400" "border-blue-400"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl">📦</h1>
📦 <h1 className="text-xl font-semibold">Portable</h1>
</h1> <p className="text-muted-foreground text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Portable</h1>
<p className="text-muted-foreground mx-auto text-center">
Download Zen as a ZIP file Download Zen as a ZIP file
</p> </p>
</div> </article>
<div <article
onClick={() => changeToFlatpak()} onClick={changeToFlatpak}
className={ny( className={ny(
"select-none w-full h-full mb-2 ml-5 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border", "select-none w-full h-full mb-2 p-5 flex flex-col items-center rounded-lg bg-background cursor-pointer border space-y-2",
selectedLinuxDownloadType === "flatpak" selectedLinuxDownloadType === "flatpak" &&
? "border-blue-400" "border-blue-400",
: "", selectedArchitecture === "generic" &&
selectedArchitecture === "generic" "opacity-50 cursor-not-allowed"
? "opacity-50 cursor-not-allowed"
: ""
)} )}
> >
<h1 className="text-5xl my-2 opacity-40 dark:opacity-20"> <h1 className="text-5xl">🧑💻</h1>
🧑💻 <h1 className="text-xl font-semibold">Flatpak</h1>
</h1> <p className="text-muted-foreground text-center text-balance text-sm">
<h1 className="text-2xl font-semibold my-2">Flatpak</h1>
<p className="text-muted-foreground mx-auto text-center">
Install Zen from the Flatpak repository. Install Zen from the Flatpak repository.
</p> </p>
</div> </article>
</div> </div>
</FormField> </FormField>
)} )}
@@ -528,19 +487,13 @@ export default function DownloadPage() {
<div className="mt-5 flex items-center justify-between"> <div className="mt-5 flex items-center justify-between">
<Button <Button
variant="ghost" variant="ghost"
onClick={() => goBackFlow()} onClick={goBackFlow}
className={ny( className={ny("opacity-70", !platform && "invisible")}
"opacity-70",
platform === null ? "invisible" : ""
)}
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />
Back Back
</Button> </Button>
<Button <Button onClick={continueFlow} disabled={!selectedPlatform}>
onClick={() => continueFlow()}
disabled={selectedPlatform === null}
>
{(flowIndex === 1 && platform === "MacOS") || flowIndex === 2 {(flowIndex === 1 && platform === "MacOS") || flowIndex === 2
? "Download 🥳" ? "Download 🥳"
: "Continue"} : "Continue"}
@@ -549,7 +502,7 @@ export default function DownloadPage() {
)} )}
{(platform === "Linux" || platform === "Windows") && {(platform === "Linux" || platform === "Windows") &&
flowIndex === 1 && ( flowIndex === 1 && (
<div className="mt-5 flex items-center"> <div className="mt-5 flex items-center justify-center">
<InfoCircledIcon className="size-4 mr-2" /> <InfoCircledIcon className="size-4 mr-2" />
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Confused about which build to choose?{" "} Confused about which build to choose?{" "}

View File

@@ -1,31 +1,49 @@
"use server"; "use server";
import { createClient } from '@supabase/supabase-js' import { createClient } from "@supabase/supabase-js";
import { releases } from "./releases";
const supabaseUrl = 'https://dmthyedfjzcysoekmyns.supabase.co' const supabaseUrl = "https://dmthyedfjzcysoekmyns.supabase.co";
const supabaseKey = process.env.SUPABASE_KEY as string; const supabaseKey = process.env.SUPABASE_KEY as string;
const supabase = createClient(supabaseUrl, supabaseKey); const supabase = createClient(supabaseUrl, supabaseKey);
export async function addDownload(platform: string) { const DOWNLOADS_TABLE = "downloads";
// Check if the download count for the platform exists const PLATFORM_COLUMN = "platform";
const COUNT_COLUMN = "count";
export async function incrementDownloadCount(platform: keyof typeof releases) {
try {
//? Check if the download count for the platform exists
const { data, error } = await supabase const { data, error } = await supabase
.from('downloads') .from(DOWNLOADS_TABLE)
.select('count') .select(COUNT_COLUMN)
.eq('platform', platform) .eq(PLATFORM_COLUMN, platform);
// If it doesn't exist, create it
console.log(data) if (error) throw new Error("Error fetching download count");
if (data?.length === 0 || data === null) {
const {data, error} = await supabase if (!data || data.length === 0) {
.from('downloads') //? If it doesn't exist, create it
.insert([{ platform, count: 1 }]); const { data: insertData, error: insertError } = await supabase
if (error) { .from(DOWNLOADS_TABLE)
console.error(error) .insert([{ platform, count: 1 }]);
}
if (insertError) throw new Error("Error inserting download count");
return insertData;
} else { } else {
// If it exists, increment the count //? If it exists, increment the count
await supabase const newCount = data![0][COUNT_COLUMN] + 1;
.from('downloads') const { data: updateData, error: updateError } = await supabase
.update({ count: data![0].count + 1 }) .from(DOWNLOADS_TABLE)
.eq('platform', platform) .update({ count: newCount })
.eq(PLATFORM_COLUMN, platform);
if (updateError) throw new Error("Error updating download count");
return updateData;
} }
} catch (err) {
console.error("Unexpected error in addDownload:", err);
throw err;
}
} }

View File

@@ -1,46 +1,54 @@
export const releases: any = { export const releases = {
WindowsInstaller: "zen.installer.exe", WindowsInstaller: "zen.installer.exe",
WindowsInstallerGeneric: "zen.installer-generic.exe", WindowsInstallerGeneric: "zen.installer-generic.exe",
WindowsZip: "zen.win-specific.zip", WindowsZip: "zen.win-specific.zip",
WindowsZipGeneric: "zen.win-generic.zip", WindowsZipGeneric: "zen.win-generic.zip",
MacOS: "zen.macos-aarch64.dmg", MacOS: "zen.macos-aarch64.dmg",
MacOSIntel: "zen.macos-x64.dmg", MacOSIntel: "zen.macos-x64.dmg",
Linux: "zen.linux-specific.tar.bz2", Linux: "zen.linux-specific.tar.bz2",
LinuxGeneric: "zen.linux-generic.tar.bz2", LinuxGeneric: "zen.linux-generic.tar.bz2",
LinuxAppImage: "zen-specific.AppImage", LinuxAppImage: "zen-specific.AppImage",
LinuxAppImageGeneric: "zen-generic.AppImage", LinuxAppImageGeneric: "zen-generic.AppImage",
}; } as const;
// platform // platform
// -> arch // -> arch
// -> file // -> file
export const releaseTree: any = { export const releaseTree: any = {
windows: { windows: {
specific: { specific: {
installer: "WindowsInstaller", installer: "WindowsInstaller",
portable: "WindowsZip", portable: "WindowsZip",
},
generic: {
installer: "WindowsInstallerGeneric",
portable: "WindowsZipGeneric",
},
}, },
macos: { generic: {
generic: "MacOSIntel", installer: "WindowsInstallerGeneric",
specific: "MacOS", portable: "WindowsZipGeneric",
}, },
linux: { },
specific: { macos: {
portable: "Linux", generic: "MacOSIntel",
appimage: "LinuxAppImage", specific: "MacOS",
}, },
generic: { linux: {
portable: "LinuxGeneric", specific: {
appimage: "LinuxAppImageGeneric", portable: "Linux",
}, appimage: "LinuxAppImage",
}, },
generic: {
portable: "LinuxGeneric",
appimage: "LinuxAppImageGeneric",
},
},
}; };
type Platform = "Windows" | "MacOS" | "Linux";
type Architecture = "specific" | "generic";
type WindowsDownloadType = "installer" | "portable";
type LinuxDownloadType = "portable" | "appimage" | "flatpak";
export type { Platform, Architecture, WindowsDownloadType, LinuxDownloadType };