improvements: revise commit history and commit details UI

This commit is contained in:
Scott Wilson
2025-07-11 20:07:53 -07:00
parent 6df6f44b50
commit 221de8beb4
4 changed files with 178 additions and 237 deletions
@@ -52,7 +52,7 @@ export const CommitsPage = () => {
title="Commits"
description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states."
/>
<NoticeBannerV2 title="" className="mb-2">
<NoticeBannerV2 title="Secret Snapshots Update" className="mb-2">
<p className="my-1 text-sm text-mineshaft-300">
Secret Snapshots have been officially renamed to Commits. Going forward, all secret
changes will be tracked as Commits. If you made changes before this update, you can
@@ -2,13 +2,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
faArrowDownWideShort,
faArrowUpWideShort,
faCodeCommit,
faCopy,
faSearch
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format, formatDistanceToNow } from "date-fns";
import { Button, Input, Spinner } from "@app/components/v2";
import { Button, ContentLoader, EmptyState, IconButton, Input } from "@app/components/v2";
import { CopyButton } from "@app/components/v2/CopyButton";
import { useGetFolderCommitHistory } from "@app/hooks/api/folderCommits";
@@ -40,58 +41,40 @@ const CommitItem = ({
onSelectCommit: (commitId: string, tab: string) => void;
}) => {
return (
<div className="border-b border-zinc-800 last:border-b-0">
<div className="px-4 py-4 transition-colors duration-200 hover:bg-zinc-800">
<div className="flex flex-col sm:flex-row sm:justify-between">
<div className="w-5/6 flex-1">
<div className="flex items-center">
<Button
variant="link"
className="truncate text-left text-white hover:underline"
isFullWidth
onClick={(e) => {
e.stopPropagation();
onSelectCommit(commit.id, "tab-commit-details");
}}
>
{commit.message}
</Button>
</div>
<p className="text-white-400 mt-2 flex flex-wrap items-center gap-4 text-sm">
<span className="flex items-center text-mineshaft-300">
{commit.actorMetadata?.email || commit.actorMetadata?.name || commit.actorType}
<p className="ml-1 mr-1">committed</p>
<time dateTime={commit.createdAt}>{formatTimeAgo(commit.createdAt)}</time>
</span>
</p>
</div>
<div className="mt-2 flex w-1/6 items-center justify-end sm:mt-0">
<div className="flex items-center space-x-1">
<Button
variant="link"
className="text-white hover:underline"
onClick={(e) => {
e.stopPropagation();
onSelectCommit(commit.id, "tab-commit-details");
}}
>
<code className="text-white-400 px-3 py-1 font-mono text-sm">
{commit.id?.substring(0, 11)}
</code>
</Button>
<CopyButton
value={commit.id}
name={commit.id}
size="sm"
variant="plain"
color="text-mineshaft-400"
icon={faCopy}
/>
</div>
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
onSelectCommit(commit.id, "tab-commit-details");
}}
className="w-full border border-b-0 border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md last:border-b"
>
<div className="flex gap-2 px-4 py-3 transition-colors duration-200 hover:bg-zinc-800">
<div className="flex flex-1 flex-col items-start">
<span className="w-min whitespace-nowrap text-sm text-mineshaft-100">
{commit.message}
</span>
<p className="text-left text-xs text-mineshaft-300">
{commit.actorMetadata?.email || commit.actorMetadata?.name || commit.actorType}{" "}
committed <time dateTime={commit.createdAt}>{formatTimeAgo(commit.createdAt)}</time>
</p>
</div>
<div className="flex items-center space-x-2">
<code className="mt-0.5 font-mono text-xs text-mineshaft-400">
{commit.id?.substring(0, 11)}
</code>
<CopyButton
value={commit.id}
name={commit.id}
size="xs"
variant="plain"
color="text-mineshaft-400"
icon={faCopy}
/>
</div>
</div>
</div>
</button>
);
};
@@ -108,24 +91,16 @@ const DateGroup = ({
onSelectCommit: (commitId: string, tab: string) => void;
}) => {
return (
<div className="mb-8 last:mb-0 last:pb-2">
<div className="mb-4 flex items-center">
<div className="relative mr-3 flex h-6 w-6 items-center justify-center">
<div className="z-10 h-3 w-3 rounded-full border-2 border-mineshaft-600 bg-bunker-800" />
<div className="absolute left-0 right-0 top-1/2 h-0.5 -translate-y-1/2 bg-mineshaft-600" />
</div>
<h2 className="text-sm text-white">Commits on {date}</h2>
<div className="mt-4 first:mt-0">
<div className="mb-4 ml-[0.15rem] flex items-center">
<FontAwesomeIcon icon={faCodeCommit} className="text-mineshaft-400" />
<h2 className="ml-4 text-sm text-mineshaft-400">Commits on {date}</h2>
</div>
<div className="relative">
<div className="absolute bottom-0 left-3 top-0 w-0.5 bg-mineshaft-600" />
<div className="absolute bottom-0 left-3 top-0 w-[0.1rem] bg-mineshaft-500" />
<div className="ml-10">
{commits.map((commit) => (
<div key={commit.id} className="relative mb-3 pb-1">
<div className="overflow-hidden rounded-md border border-solid border-mineshaft-600">
<CommitItem commit={commit} onSelectCommit={onSelectCommit} />
</div>
</div>
<CommitItem key={commit.id} commit={commit} onSelectCommit={onSelectCommit} />
))}
</div>
</div>
@@ -150,7 +125,7 @@ export const CommitHistoryTab = ({
const [offset, setOffset] = useState(0);
const [allCommits, setAllCommits] = useState<Commit[]>([]);
const debounceTimeoutRef = useRef<NodeJS.Timeout>();
const limit = 5;
const limit = 10;
// Debounce search term
useEffect(() => {
@@ -234,42 +209,37 @@ export const CommitHistoryTab = ({
}, [hasMore, isFetching, limit]);
return (
<div className="w-full">
<div className="mt-4 w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Commit History</p>
<div className="mb-4 flex flex-col sm:flex-row sm:justify-end">
<div className="flex w-full flex-wrap items-center gap-2">
<div className="relative flex-grow">
<Input
leftIcon={<FontAwesomeIcon icon={faSearch} aria-hidden="true" />}
placeholder="Search commits..."
className="h-10 w-full rounded-md border-transparent bg-zinc-800 pl-9 pr-3 text-sm text-white placeholder-gray-400 focus:border-gray-600 focus:ring-primary-500/20"
onChange={(e) => handleSearch(e.target.value)}
value={searchTerm}
aria-label="Search commits"
/>
<div className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400">
<FontAwesomeIcon icon={faSearch} aria-hidden="true" />
</div>
</div>
<Button
<IconButton
variant="outline_bg"
size="md"
className="flex h-10 items-center justify-center gap-2 rounded-md bg-zinc-800 px-4 py-2 text-sm text-white transition-colors duration-200 hover:bg-zinc-700 focus:outline-none focus:ring-2 focus:ring-primary-500"
size="sm"
className="flex h-[2.4rem] items-center justify-center gap-2 rounded-md"
onClick={handleSort}
aria-label={`Sort by date ${sortDirection === "desc" ? "ascending" : "descending"}`}
ariaLabel={`Sort by date ${sortDirection === "desc" ? "ascending" : "descending"}`}
>
<FontAwesomeIcon
icon={sortDirection === "desc" ? faArrowDownWideShort : faArrowUpWideShort}
aria-hidden="true"
/>
</Button>
</IconButton>
</div>
</div>
{isLoading && offset === 0 ? (
<div className="flex h-64 items-center justify-center">
<Spinner size="lg" aria-label="Loading commits" />
</div>
<ContentLoader className="h-80" />
) : (
<div className="space-y-8">
<div className="">
{Object.keys(groupedCommits).length > 0 ? (
<>
{Object.entries(groupedCommits).map(([date, dateCommits]) => (
@@ -282,34 +252,21 @@ export const CommitHistoryTab = ({
))}
</>
) : (
<div className="text-white-400 flex min-h-40 flex-col items-center justify-center rounded-lg bg-zinc-900 py-8 text-center">
<FontAwesomeIcon
icon={faSearch}
className="text-white-500 mb-3 text-3xl"
aria-hidden="true"
/>
<p>No matching commits found. Try a different search term.</p>
</div>
<EmptyState title="No commits found." icon={faCodeCommit} />
)}
{hasMore && (
<div className="flex justify-center pb-2">
<Button
variant="outline_bg"
size="md"
className="rounded-md bg-zinc-900 px-6 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-primary-500"
size="sm"
className="ml-10 mt-4 w-full"
onClick={loadMoreCommits}
disabled={isFetching}
isLoading={isFetching}
aria-label="Load more commits"
>
{isFetching ? (
<>
<Spinner size="sm" className="mr-2" />
Loading...
</>
) : (
"Load more commits"
)}
Load More Commits
</Button>
</div>
)}