CommitDialog.svelte

  1<script lang="ts">
  2	import { showCommitDialog } from '$lib/stores/ui';
  3	import { uncommittedCount, loadUncommittedFiles, vcs } from '$lib/stores/files';
  4
  5	let message = $state('');
  6	let commitMode = $state<'approved' | 'all'>('approved');
  7	let committing = $state(false);
  8	let result = $state<{ success: boolean; hash?: string; error?: string } | null>(null);
  9
 10	// Refresh uncommitted file counts when dialog opens
 11	$effect(() => {
 12		if ($showCommitDialog) {
 13			loadUncommittedFiles();
 14		}
 15	});
 16
 17	async function handleCommit() {
 18		if (!message.trim()) return;
 19		committing = true;
 20		result = null;
 21
 22		try {
 23			const res = await fetch('/api/commit', {
 24				method: 'POST',
 25				headers: { 'Content-Type': 'application/json' },
 26				body: JSON.stringify({ message: message.trim(), mode: commitMode })
 27			});
 28			result = await res.json();
 29		} catch (err) {
 30			result = { success: false, error: String(err) };
 31		} finally {
 32			committing = false;
 33		}
 34	}
 35
 36	function close() {
 37		$showCommitDialog = false;
 38		message = '';
 39		result = null;
 40	}
 41
 42	function handleKeydown(e: KeyboardEvent) {
 43		if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
 44			e.preventDefault();
 45			handleCommit();
 46		}
 47		if (e.key === 'Escape') close();
 48	}
 49</script>
 50
 51{#if $showCommitDialog && $vcs !== 'jj'}
 52	<!-- svelte-ignore a11y_click_events_have_key_events -->
 53	<!-- svelte-ignore a11y_no_static_element_interactions -->
 54	<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={close}>
 55		<!-- svelte-ignore a11y_click_events_have_key_events -->
 56		<!-- svelte-ignore a11y_no_static_element_interactions -->
 57		<div class="bg-panel border border-border-strong rounded-xl shadow-2xl w-full max-w-lg mx-4" onclick={(e) => e.stopPropagation()}>
 58			<div class="px-5 py-4 border-b border-border">
 59				<h2 class="text-lg font-semibold text-primary">Commit Changes</h2>
 60			</div>
 61
 62			{#if result?.success}
 63				<div class="p-5">
 64					<div class="text-center">
 65						<div class="text-accent-green text-4xl mb-3">✓</div>
 66						<p class="text-primary font-medium">Committed successfully</p>
 67						<p class="text-tertiary text-sm mt-1">Hash: {result.hash}</p>
 68					</div>
 69				</div>
 70				<div class="px-5 py-3 border-t border-border flex justify-end">
 71					<button
 72						class="text-sm px-4 py-2 rounded bg-active text-primary hover:opacity-80 transition-colors"
 73						onclick={close}
 74					>
 75						Done
 76					</button>
 77				</div>
 78			{:else}
 79				<div class="p-5 space-y-4">
 80					<!-- Commit scope -->
 81					<div>
 82						<div class="block text-sm text-tertiary mb-2">What to commit</div>
 83						<div class="flex gap-2">
 84							<button
 85								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'approved' ? 'border-green-600 bg-accent-green/10 text-accent-green' : 'border-border-strong text-tertiary hover:border-border-strong'}"
 86								onclick={() => (commitMode = 'approved')}
 87							>
 88								<div class="font-medium">Approved files only</div>
 89								<div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.approved} of {$uncommittedCount.total} files</div>
 90							</button>
 91							<button
 92								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'all' ? 'border-blue-600 bg-accent-blue/10 text-accent-blue' : 'border-border-strong text-tertiary hover:border-border-strong'}"
 93								onclick={() => (commitMode = 'all')}
 94							>
 95								<div class="font-medium">All changes</div>
 96								<div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.total} files</div>
 97							</button>
 98						</div>
 99					</div>
100
101					{#if commitMode === 'approved' && $uncommittedCount.approved === 0}
102						<div class="text-sm rounded-lg px-3 py-2 bg-accent-yellow/10 text-accent-yellow">
103							No files approved yet. Approve files in the sidebar first, or commit all changes.
104						</div>
105					{/if}
106
107					<!-- Commit message -->
108					<div>
109						<label class="block text-sm text-tertiary mb-2" for="commit-msg">Commit message</label>
110						<textarea
111							id="commit-msg"
112							bind:value={message}
113							onkeydown={handleKeydown}
114							class="w-full bg-surface text-primary border border-border-strong rounded-lg px-3 py-2 text-sm resize-y min-h-[100px] focus:outline-none focus:border-blue-500 placeholder:text-muted"
115							placeholder="Describe your changes..."
116							rows="4"
117						></textarea>
118					</div>
119
120					{#if result?.error}
121						<p class="text-accent-red text-sm">{result.error}</p>
122					{/if}
123				</div>
124
125				<div class="px-5 py-3 border-t border-border flex items-center justify-between">
126					<span class="text-xs text-muted">
127						{navigator?.platform?.includes('Mac') ? '⌘' : 'Ctrl'}+Enter to commit
128					</span>
129					<div class="flex gap-2">
130						<button
131							class="text-sm px-4 py-2 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
132							onclick={close}
133						>
134							Cancel
135						</button>
136						<button
137							class="text-sm px-4 py-2 rounded bg-green-600 text-white hover:bg-green-500 transition-colors disabled:opacity-50"
138							disabled={!message.trim() || committing || (commitMode === 'approved' && $uncommittedCount.approved === 0)}
139							onclick={handleCommit}
140						>
141							{committing ? 'Committing...' : commitMode === 'approved' ? `Commit ${$uncommittedCount.approved} files` : 'Commit all'}
142						</button>
143					</div>
144				</div>
145			{/if}
146		</div>
147	</div>
148{/if}