+ {PATCH_DOWNLOAD_OPTIONS.map((opt) => {
+ const isUiSelected = selectedPermission === opt.value;
+ const showSavedBadge = savedPermission === opt.value && dirty;
+
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/Hack/VersionList.tsx b/src/components/Hack/VersionList.tsx
index 77f9408..dc0a435 100644
--- a/src/components/Hack/VersionList.tsx
+++ b/src/components/Hack/VersionList.tsx
@@ -5,7 +5,8 @@ import Markdown from "@/components/Markdown/Markdown";
import { FaChevronDown, FaChevronUp, FaStar, FaDownload, FaTrash, FaRotateLeft, FaUpload, FaCheck, FaPlus } from "react-icons/fa6";
import { FiEdit2, FiEdit, FiX } from "react-icons/fi";
import VersionActions from "@/components/Hack/VersionActions";
-import { updatePatchChangelog, updatePatchVersion } from "@/app/hack/[slug]/actions";
+import type { PatchesDownloadPermission } from "@/components/Hack/DownloadPermissionSettings";
+import { updatePatchChangelog, updatePatchVersion, getPatchDownloadUrl } from "@/app/hack/[slug]/actions";
import { useRouter } from "next/navigation";
import { createClient } from "@/utils/supabase/client";
@@ -19,15 +20,68 @@ interface Patch {
archived: boolean;
}
+function shouldShowPublicPatchDownload(
+ permission: PatchesDownloadPermission,
+ patch: Patch,
+ isCurrent: boolean,
+): boolean {
+ if (permission === "None") return false;
+ if (!patch.published || patch.archived) return false;
+ if (permission === "All") return true;
+ if (permission === "Current") return isCurrent;
+ return false;
+}
+
+function PublicPatchDownloadButton({ patchId }: { patchId: number }) {
+ const [loading, setLoading] = useState(false);
+
+ const handleClick = async () => {
+ setLoading(true);
+ try {
+ const result = await getPatchDownloadUrl(patchId);
+ if (result.ok) {
+ window.open(result.url, "_blank");
+ } else {
+ alert(result.error || "Failed to generate download URL");
+ }
+ } catch {
+ alert("Failed to download patch");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+ Created: {new Date(patch.created_at).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+ {patch.updated_at && patch.updated_at !== patch.created_at && (
+
+ Updated: {new Date(patch.updated_at).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+ )}
+
+ );
return (