diff --git a/apps/dashboard/src/components/repo/code-explorer-toolbar.tsx b/apps/dashboard/src/components/repo/code-explorer-toolbar.tsx
index ce948f7..16f5fc4 100644
--- a/apps/dashboard/src/components/repo/code-explorer-toolbar.tsx
+++ b/apps/dashboard/src/components/repo/code-explorer-toolbar.tsx
@@ -33,6 +33,7 @@ import {
githubRepoBranchesQueryOptions,
} from "#/lib/github.query";
import type { RepoOverview } from "#/lib/github.types";
+import { useRepoCloneProtocol } from "#/lib/repo-clone-protocol-storage";
export function CodeExplorerToolbar({
repo,
@@ -177,12 +178,20 @@ function BranchSelector({
}
function CodePopover({ repo }: { repo: RepoOverview }) {
+ const [selectedProtocol, setSelectedProtocol] = useRepoCloneProtocol();
const [copied, setCopied] = useState(false);
const httpsUrl = `https://github.com/${repo.fullName}.git`;
const sshUrl = `git@github.com:${repo.fullName}.git`;
const cliCommand = `gh repo clone ${repo.fullName}`;
const zipUrl = `https://github.com/${repo.fullName}/archive/refs/heads/${repo.defaultBranch}.zip`;
+ const handleProtocolChange = useCallback(
+ (protocol: string) => {
+ setSelectedProtocol(protocol);
+ },
+ [setSelectedProtocol],
+ );
+
const handleCopy = useCallback((text: string) => {
void navigator.clipboard.writeText(text);
setCopied(true);
@@ -203,7 +212,11 @@ function CodePopover({ repo }: { repo: RepoOverview }) {
Clone {repo.fullName}
-
+
HTTPS
diff --git a/apps/dashboard/src/lib/repo-clone-protocol-storage.ts b/apps/dashboard/src/lib/repo-clone-protocol-storage.ts
new file mode 100644
index 0000000..2eb5476
--- /dev/null
+++ b/apps/dashboard/src/lib/repo-clone-protocol-storage.ts
@@ -0,0 +1,40 @@
+import { useCallback } from "react";
+import { useLocalStorageState } from "./use-local-storage-state";
+
+export type CloneProtocol = "https" | "ssh" | "cli";
+
+const CLONE_PROTOCOL_STORAGE_KEY = "diffkit:repo-clone-protocol";
+const DEFAULT_CLONE_PROTOCOL: CloneProtocol = "cli";
+
+const VALID_CLONE_PROTOCOLS = {
+ https: true,
+ ssh: true,
+ cli: true,
+} satisfies Record;
+
+export function isCloneProtocol(value: unknown): value is CloneProtocol {
+ return typeof value === "string" && value in VALID_CLONE_PROTOCOLS;
+}
+
+export function useRepoCloneProtocol() {
+ const [cloneProtocol, setCloneProtocol] = useLocalStorageState(
+ CLONE_PROTOCOL_STORAGE_KEY,
+ {
+ defaultValue: DEFAULT_CLONE_PROTOCOL,
+ validate: isCloneProtocol,
+ },
+ );
+
+ const setStoredCloneProtocol = useCallback(
+ (protocol: string) => {
+ if (!isCloneProtocol(protocol)) {
+ return;
+ }
+
+ setCloneProtocol(protocol);
+ },
+ [setCloneProtocol],
+ );
+
+ return [cloneProtocol, setStoredCloneProtocol] as const;
+}
diff --git a/apps/dashboard/src/lib/use-local-storage-state.ts b/apps/dashboard/src/lib/use-local-storage-state.ts
new file mode 100644
index 0000000..236a275
--- /dev/null
+++ b/apps/dashboard/src/lib/use-local-storage-state.ts
@@ -0,0 +1,58 @@
+import { useCallback, useState } from "react";
+
+type LocalStorageStateOptions = {
+ defaultValue: T;
+ validate: (value: unknown) => value is T;
+ parse?: (raw: string) => unknown;
+ serialize?: (value: T) => string;
+};
+
+export function useLocalStorageState(
+ key: string,
+ {
+ defaultValue,
+ validate,
+ parse = (raw) => raw,
+ serialize = String,
+ }: LocalStorageStateOptions,
+) {
+ const [value, setValue] = useState(() => {
+ if (typeof window === "undefined") {
+ return defaultValue;
+ }
+
+ try {
+ const stored = window.localStorage.getItem(key);
+ if (stored === null) {
+ return defaultValue;
+ }
+
+ const parsed = parse(stored);
+ return validate(parsed) ? parsed : defaultValue;
+ } catch {
+ return defaultValue;
+ }
+ });
+
+ const setStoredValue = useCallback(
+ (nextValue: T | ((currentValue: T) => T)) => {
+ setValue((currentValue) => {
+ const resolvedValue =
+ typeof nextValue === "function"
+ ? (nextValue as (currentValue: T) => T)(currentValue)
+ : nextValue;
+
+ try {
+ window.localStorage.setItem(key, serialize(resolvedValue));
+ } catch {
+ // ignore
+ }
+
+ return resolvedValue;
+ });
+ },
+ [key, serialize],
+ );
+
+ return [value, setStoredValue] as const;
+}