mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat(branch): Improve styling
This commit is contained in:
committed by
Sam Chau
parent
650cfa2dc7
commit
06a230eeba
@@ -1,17 +1,34 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Modal from '../ui/Modal';
|
||||
import { getBranches, checkoutBranch, createBranch, deleteBranch } from '../../api/api';
|
||||
import { ExternalLink, Trash2, GitBranchPlus, ArrowRightCircle, Loader } from 'lucide-react';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
import Alert from '../ui/Alert';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Modal from "../ui/Modal";
|
||||
import {
|
||||
getBranches,
|
||||
checkoutBranch,
|
||||
createBranch,
|
||||
deleteBranch,
|
||||
} from "../../api/api";
|
||||
import {
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
GitBranchPlus,
|
||||
ArrowRightCircle,
|
||||
Loader,
|
||||
} from "lucide-react";
|
||||
import Tooltip from "../ui/Tooltip";
|
||||
import Alert from "../ui/Alert";
|
||||
|
||||
const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranchChange }) => {
|
||||
const SettingsBranchModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
repoUrl,
|
||||
currentBranch,
|
||||
onBranchChange,
|
||||
}) => {
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [branchOffMode, setBranchOffMode] = useState(null);
|
||||
const [newBranchName, setNewBranchName] = useState('');
|
||||
const [newBranchName, setNewBranchName] = useState("");
|
||||
const [validBranchName, setValidBranchName] = useState(true);
|
||||
const [branchToDelete, setBranchToDelete] = useState(null);
|
||||
const [loadingAction, setLoadingAction] = useState('');
|
||||
const [loadingAction, setLoadingAction] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -26,19 +43,19 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
if (response.success && response.data.branches) {
|
||||
setBranches(response.data.branches);
|
||||
} else {
|
||||
console.error('Error fetching branches:', response.data.error);
|
||||
console.error("Error fetching branches:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching branches:', error);
|
||||
console.error("Error fetching branches:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setBranchOffMode(null);
|
||||
setNewBranchName('');
|
||||
setNewBranchName("");
|
||||
setValidBranchName(true);
|
||||
setBranchToDelete(null);
|
||||
setLoadingAction('');
|
||||
setLoadingAction("");
|
||||
};
|
||||
|
||||
const handleCheckout = async (branchName, isNewBranch = false) => {
|
||||
@@ -49,56 +66,68 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
// Refresh branches after successful checkout
|
||||
await fetchBranches();
|
||||
// Notify parent component to update the status
|
||||
onBranchChange(); // <-- Call the callback to update status in the parent component
|
||||
onBranchChange(); // <-- Call the callback to update status in the parent component
|
||||
if (!isNewBranch) {
|
||||
Alert.success('Branch checked out successfully');
|
||||
Alert.success("Branch checked out successfully");
|
||||
}
|
||||
onClose(); // Close the modal
|
||||
} else {
|
||||
Alert.error(response.error); // Use the Alert component for error
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400 && error.response.data.error) {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.status === 400 &&
|
||||
error.response.data.error
|
||||
) {
|
||||
Alert.error(error.response.data.error); // Show alert with specific backend error message
|
||||
} else {
|
||||
Alert.error('An unexpected error occurred while checking out the branch.');
|
||||
console.error('Error checking out branch:', error);
|
||||
Alert.error(
|
||||
"An unexpected error occurred while checking out the branch."
|
||||
);
|
||||
console.error("Error checking out branch:", error);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleBranchOff = async () => {
|
||||
setLoadingAction('branchOff');
|
||||
setLoadingAction("branchOff");
|
||||
if (newBranchName && validBranchName) {
|
||||
try {
|
||||
const response = await createBranch(newBranchName, branchOffMode);
|
||||
if (response.success) {
|
||||
// Checkout the new branch without showing the checkout alert
|
||||
await handleCheckout(newBranchName, true);
|
||||
Alert.success('Branch created and checked out successfully');
|
||||
Alert.success("Branch created and checked out successfully");
|
||||
} else {
|
||||
Alert.error(response.error); // Handle known errors from the backend
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400 && error.response.data.error) {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.status === 400 &&
|
||||
error.response.data.error
|
||||
) {
|
||||
Alert.error(error.response.data.error); // Specific error from the backend
|
||||
} else {
|
||||
console.error('Error branching off:', error); // Log unexpected errors
|
||||
Alert.error('An unexpected error occurred while creating the branch. Please try again.');
|
||||
console.error("Error branching off:", error); // Log unexpected errors
|
||||
Alert.error(
|
||||
"An unexpected error occurred while creating the branch. Please try again."
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
setLoadingAction("");
|
||||
}
|
||||
} else {
|
||||
Alert.error('Please enter a valid branch name.');
|
||||
Alert.error("Please enter a valid branch name.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleBranchOffClick = (branchName) => {
|
||||
setBranchOffMode(branchName);
|
||||
setNewBranchName('');
|
||||
setNewBranchName("");
|
||||
setValidBranchName(true);
|
||||
};
|
||||
|
||||
@@ -110,7 +139,7 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
|
||||
const handleOpenInGitHub = (branchName) => {
|
||||
const branchUrl = `${repoUrl}/tree/${encodeURIComponent(branchName)}`;
|
||||
window.open(branchUrl, '_blank');
|
||||
window.open(branchUrl, "_blank");
|
||||
};
|
||||
|
||||
const confirmDeleteBranch = (branchName) => {
|
||||
@@ -118,7 +147,7 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
};
|
||||
|
||||
const handleDeleteBranch = async () => {
|
||||
if (branchToDelete && branchToDelete.toLowerCase() === 'main') {
|
||||
if (branchToDelete && branchToDelete.toLowerCase() === "main") {
|
||||
Alert.warning("The 'main' branch cannot be deleted.");
|
||||
return;
|
||||
}
|
||||
@@ -126,7 +155,7 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
try {
|
||||
const response = await deleteBranch(branchToDelete);
|
||||
if (response.success) {
|
||||
onBranchChange(); // <-- Call the callback to update status in the parent component
|
||||
onBranchChange(); // <-- Call the callback to update status in the parent component
|
||||
await fetchBranches(); // Refresh the list after deletion
|
||||
Alert.success(`Branch '${branchToDelete}' deleted successfully`);
|
||||
setBranchToDelete(null);
|
||||
@@ -134,43 +163,60 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
Alert.error(response.error); // Use the Alert component for error
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.error('An unexpected error occurred while deleting the branch.');
|
||||
console.error('Error deleting branch:', error);
|
||||
Alert.error("An unexpected error occurred while deleting the branch.");
|
||||
console.error("Error deleting branch:", error);
|
||||
} finally {
|
||||
setLoadingAction('');
|
||||
setLoadingAction("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Manage Git Branches">
|
||||
<div className="space-y-4 text-xs">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-2 text-gray-100">Branches:</h3>
|
||||
<ul className="space-y-2">
|
||||
<div className="space-y-6 text-sm">
|
||||
<div className="bg-gray-800 rounded-lg p-4 shadow-inner">
|
||||
<h3 className="text-lg font-semibold mb-3 text-gray-100 border-b border-gray-700 pb-2">
|
||||
Branches
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{branches.map((branch, index) => (
|
||||
<li
|
||||
key={index} // Ensure you use a unique key
|
||||
className={`flex items-center justify-between p-2 rounded ${
|
||||
branch.name === currentBranch ? 'border border-blue-400 bg-gray-800' : 'bg-gray-700'
|
||||
key={index}
|
||||
className={`flex items-center justify-between p-3 rounded-md transition-all duration-200 ${
|
||||
branch.name === currentBranch
|
||||
? "bg-blue-900/30 border border-blue-500 shadow-md"
|
||||
: "bg-gray-700 hover:bg-gray-650"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className={branch.name === currentBranch ? 'text-white' : 'text-gray-300'}>
|
||||
{branch.name ? branch.name : 'Unknown Branch'} {/* Fallback if branch.name is undefined */}
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
branch.name === currentBranch
|
||||
? "bg-blue-400"
|
||||
: "bg-gray-400"
|
||||
}`}
|
||||
></div>
|
||||
<span
|
||||
className={`font-medium ${
|
||||
branch.name === currentBranch
|
||||
? "text-blue-200"
|
||||
: "text-gray-200"
|
||||
}`}
|
||||
>
|
||||
{branch.name || "Unknown Branch"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
{branch.name !== currentBranch && (
|
||||
<Tooltip content="Checkout">
|
||||
<button
|
||||
onClick={() => handleCheckout(branch.name)}
|
||||
className="p-1 bg-blue-500 text-white rounded hover:bg-blue-600 hover:scale-105 transition-transform duration-200"
|
||||
className="p-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === `checkout-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `checkout-${branch.name}` ? (
|
||||
<Loader size={14} className="animate-spin" />
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<ArrowRightCircle size={14} />
|
||||
<ArrowRightCircle size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -178,37 +224,38 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
<Tooltip content="Branch Off">
|
||||
<button
|
||||
onClick={() => handleBranchOffClick(branch.name)}
|
||||
className="p-1 bg-green-500 text-white rounded hover:bg-green-600 hover:scale-105 transition-transform duration-200"
|
||||
disabled={loadingAction === 'branchOff'}
|
||||
className="p-1.5 bg-green-500 text-white rounded-md hover:bg-green-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === "branchOff"}
|
||||
>
|
||||
{loadingAction === 'branchOff' ? (
|
||||
<Loader size={14} className="animate-spin" />
|
||||
{loadingAction === "branchOff" ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<GitBranchPlus size={14} />
|
||||
<GitBranchPlus size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
{branch.name !== currentBranch && branch.name.toLowerCase() !== 'main' && (
|
||||
<Tooltip content="Delete">
|
||||
<button
|
||||
onClick={() => confirmDeleteBranch(branch.name)}
|
||||
className="p-1 bg-red-500 text-white rounded hover:bg-red-600 hover:scale-105 transition-transform duration-200"
|
||||
disabled={loadingAction === `delete-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `delete-${branch.name}` ? (
|
||||
<Loader size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Trash2 size={14} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{branch.name !== currentBranch &&
|
||||
branch.name.toLowerCase() !== "main" && (
|
||||
<Tooltip content="Delete">
|
||||
<button
|
||||
onClick={() => confirmDeleteBranch(branch.name)}
|
||||
className="p-1.5 bg-red-500 text-white rounded-md hover:bg-red-600 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
disabled={loadingAction === `delete-${branch.name}`}
|
||||
>
|
||||
{loadingAction === `delete-${branch.name}` ? (
|
||||
<Loader size={16} className="animate-spin" />
|
||||
) : (
|
||||
<Trash2 size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip content="View on GitHub">
|
||||
<button
|
||||
onClick={() => handleOpenInGitHub(branch.name)}
|
||||
className="p-1 bg-gray-600 text-white rounded hover:bg-gray-700 hover:scale-105 transition-transform duration-200"
|
||||
className="p-1.5 bg-gray-600 text-white rounded-md hover:bg-gray-500 hover:scale-105 transition-all duration-200 shadow-sm"
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
<ExternalLink size={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -217,37 +264,54 @@ const SettingsBranchModal = ({ isOpen, onClose, repoUrl, currentBranch, onBranch
|
||||
</ul>
|
||||
</div>
|
||||
{branchOffMode && (
|
||||
<div className="flex items-center space-x-2 mt-4">
|
||||
<input
|
||||
type="text"
|
||||
value={newBranchName}
|
||||
onChange={(e) => validateBranchName(e.target.value)}
|
||||
placeholder={`New branch from ${branchOffMode}`}
|
||||
className={`flex-grow p-2 border rounded bg-gray-800 text-gray-300 ${!validBranchName ? 'border-red-500' : ''}`}
|
||||
/>
|
||||
<button
|
||||
onClick={handleBranchOff}
|
||||
disabled={!newBranchName || !validBranchName || loadingAction === 'branchOff'}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors text-xs"
|
||||
>
|
||||
{loadingAction === 'branchOff' ? 'Creating...' : 'Create'}
|
||||
</button>
|
||||
<div className="bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<h4 className="text-sm font-semibold mb-2 text-gray-200">
|
||||
Create New Branch
|
||||
</h4>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newBranchName}
|
||||
onChange={(e) => validateBranchName(e.target.value)}
|
||||
placeholder={`New branch from ${branchOffMode}`}
|
||||
className={`flex-grow p-2 border rounded bg-gray-800 text-gray-300 focus:ring-2 focus:ring-blue-500 transition-all ${
|
||||
!validBranchName ? "border-red-500" : "border-gray-600"
|
||||
}`}
|
||||
/>
|
||||
<button
|
||||
onClick={handleBranchOff}
|
||||
disabled={
|
||||
!newBranchName ||
|
||||
!validBranchName ||
|
||||
loadingAction === "branchOff"
|
||||
}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
{loadingAction === "branchOff" ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{branchToDelete && (
|
||||
<div className="mt-4 text-sm text-gray-200">
|
||||
<p>Are you sure you want to delete the branch <strong>{branchToDelete}</strong>? This action cannot be undone.</p>
|
||||
<div className="flex space-x-4 mt-2">
|
||||
<div className="bg-red-900/30 border border-red-500 rounded-lg p-4 mt-4 text-sm text-gray-200">
|
||||
<p className="mb-3">
|
||||
Are you sure you want to delete the branch{" "}
|
||||
<strong className="text-red-300">{branchToDelete}</strong>? This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
onClick={handleDeleteBranch}
|
||||
disabled={loadingAction === `delete-${branchToDelete}`}
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-xs"
|
||||
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
{loadingAction === `delete-${branchToDelete}` ? 'Deleting...' : 'Confirm Delete'}
|
||||
{loadingAction === `delete-${branchToDelete}`
|
||||
? "Deleting..."
|
||||
: "Confirm Delete"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBranchToDelete(null)}
|
||||
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition-colors text-xs"
|
||||
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600 transition-colors text-sm font-medium shadow-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user