Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion apps/dashboard/src/components/repo/code-explorer-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -203,7 +212,11 @@ function CodePopover({ repo }: { repo: RepoOverview }) {
<span className="text-sm font-semibold">Clone {repo.fullName}</span>
</div>

<Tabs defaultValue="cli" className="gap-0">
<Tabs
value={selectedProtocol}
onValueChange={handleProtocolChange}
className="gap-0"
>
<TabsList className="mx-4 mt-3 h-8 w-fit">
<TabsTrigger value="https" className="text-xs">
HTTPS
Expand Down
40 changes: 40 additions & 0 deletions apps/dashboard/src/lib/repo-clone-protocol-storage.ts
Original file line number Diff line number Diff line change
@@ -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<CloneProtocol, true>;

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;
}
58 changes: 58 additions & 0 deletions apps/dashboard/src/lib/use-local-storage-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useCallback, useState } from "react";

type LocalStorageStateOptions<T> = {
defaultValue: T;
validate: (value: unknown) => value is T;
parse?: (raw: string) => unknown;
serialize?: (value: T) => string;
};

export function useLocalStorageState<T>(
key: string,
{
defaultValue,
validate,
parse = (raw) => raw,
serialize = String,
}: LocalStorageStateOptions<T>,
) {
const [value, setValue] = useState<T>(() => {
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;
}
Loading