diff --git a/backend/app/git/__init__.py b/backend/app/git/__init__.py
index 2d88672..45a3091 100644
--- a/backend/app/git/__init__.py
+++ b/backend/app/git/__init__.py
@@ -5,6 +5,7 @@ from .branches.manager import Branch_Manager
from .operations.manager import GitOperations
from .repo.unlink import unlink_repository
from .repo.clone import clone_repository
+from .auth.authenticate import check_dev_mode
from ..settings_utils import save_settings
import logging
@@ -249,3 +250,9 @@ def generate_commit_message(user_message, files):
commit_message = f"{user_message}\n\nChanges:\n" + "\n".join(file_changes)
return commit_message
+
+
+@bp.route('/dev', methods=['GET'])
+def dev_mode():
+ is_dev_mode = check_dev_mode()
+ return jsonify({'devMode': is_dev_mode}), 200
diff --git a/backend/app/git/auth/authenticate.py b/backend/app/git/auth/authenticate.py
index 797b9c6..8dde86a 100644
--- a/backend/app/git/auth/authenticate.py
+++ b/backend/app/git/auth/authenticate.py
@@ -2,33 +2,95 @@
import subprocess
import logging
+import os
+from ...settings_utils import load_settings
logger = logging.getLogger(__name__)
+
+def get_github_token():
+ token = os.environ.get('GITHUB_TOKEN')
+ if token:
+ logger.info("GitHub token retrieved from environment variable")
+ else:
+ logger.warning("GitHub token not found in environment variables")
+ return token
+
+
def validate_git_token(repo_url, git_token):
+ logger.info(f"Validating git token for repo: {repo_url}")
try:
parts = repo_url.strip('/').split('/')
if len(parts) < 2:
+ logger.error(f"Invalid repo URL format: {repo_url}")
return False
-
repo_owner, repo_name = parts[-2], parts[-1].replace('.git', '')
+ logger.info(f"Parsed repo owner: {repo_owner}, repo name: {repo_name}")
+
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}"
+ logger.debug(f"GitHub API URL: {api_url}")
curl_command = [
- 'curl', '-s', '-o', '/dev/null', '-w', '%{http_code}',
- '-H', f'Authorization: Bearer {git_token}',
- '-H', 'Accept: application/vnd.github+json',
- api_url
+ 'curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', '-H',
+ f'Authorization: Bearer {git_token}', '-H',
+ 'Accept: application/vnd.github+json', api_url
]
-
+ logger.debug(f"Executing curl command: {' '.join(curl_command)}")
result = subprocess.run(curl_command, capture_output=True, text=True)
http_status_code = int(result.stdout.strip())
+ logger.info(f"API response status code: {http_status_code}")
if http_status_code == 200:
- return True
- elif http_status_code == 401:
- return False
+ logger.info(
+ "Token has access to the repository. Checking write permissions."
+ )
+ permissions_url = f"{api_url}/collaborators/{repo_owner}/permission"
+ logger.debug(f"Permissions check URL: {permissions_url}")
+ permissions_command = [
+ 'curl', '-s', '-H', f'Authorization: Bearer {git_token}', '-H',
+ 'Accept: application/vnd.github+json', permissions_url
+ ]
+ logger.debug(
+ f"Executing permissions check command: {' '.join(permissions_command)}"
+ )
+ permissions_result = subprocess.run(permissions_command,
+ capture_output=True,
+ text=True)
+ permissions_data = permissions_result.stdout.strip()
+ logger.debug(f"Permissions data: {permissions_data}")
+
+ has_write_access = any(perm in permissions_data
+ for perm in ['admin', 'write', 'maintain'])
+ logger.info(f"Write access: {has_write_access}")
+ return has_write_access
else:
+ logger.warning(
+ f"Token validation failed with status code: {http_status_code}"
+ )
return False
except Exception as e:
- return False
\ No newline at end of file
+ logger.exception(f"Error validating git token: {str(e)}")
+ return False
+
+
+def check_dev_mode():
+ logger.info("Checking dev mode")
+ settings = load_settings()
+ if not settings:
+ logger.warning("Settings not found")
+ return False
+ if 'gitRepo' not in settings:
+ logger.warning("Git repo URL not found in settings")
+ return False
+
+ repo_url = settings['gitRepo']
+ logger.info(f"Git repo URL from settings: {repo_url}")
+
+ github_token = get_github_token()
+ if not github_token:
+ logger.warning("GitHub token not available")
+ return False
+
+ is_dev_mode = validate_git_token(repo_url, github_token)
+ logger.info(f"Dev mode status: {is_dev_mode}")
+ return is_dev_mode
diff --git a/docker-compose.yml b/docker-compose.yml
index 9034625..408a654 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -20,5 +20,7 @@ services:
- backend_data:/app/data
environment:
- FLASK_ENV=development
+ env_file:
+ - .env
volumes:
backend_data:
diff --git a/frontend/src/api/api.js b/frontend/src/api/api.js
index 292e30e..3f2656c 100644
--- a/frontend/src/api/api.js
+++ b/frontend/src/api/api.js
@@ -3,336 +3,356 @@ import axios from 'axios';
const API_BASE_URL = 'http://localhost:5000';
export const getRegexes = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/regex`);
- return response.data;
- } catch (error) {
- console.error('Error fetching regexes:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/regex`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching regexes:', error);
+ throw error;
+ }
};
export const saveRegex = async regex => {
- try {
- const response = await axios.post(`${API_BASE_URL}/regex`, regex);
- return response.data;
- } catch (error) {
- console.error('Error saving regex:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/regex`, regex);
+ return response.data;
+ } catch (error) {
+ console.error('Error saving regex:', error);
+ throw error;
+ }
};
export const updateRegex = async (id, regex) => {
- try {
- const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex);
- return response.data;
- } catch (error) {
- console.error('Error updating regex:', error);
- throw error;
- }
+ try {
+ const response = await axios.put(`${API_BASE_URL}/regex/${id}`, regex);
+ return response.data;
+ } catch (error) {
+ console.error('Error updating regex:', error);
+ throw error;
+ }
};
export const deleteRegex = async (id, force = false) => {
- try {
- const response = await axios.delete(
- `${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`,
- {
- validateStatus: status => {
- return (
- (status >= 200 && status < 300) || status === 400 || status === 409
- );
- }
- }
- );
- return response.data;
- } catch (error) {
- console.error('Error deleting regex:', error);
- throw error;
- }
+ try {
+ const response = await axios.delete(
+ `${API_BASE_URL}/regex/${id}${force ? '?force=true' : ''}`,
+ {
+ validateStatus: status => {
+ return (
+ (status >= 200 && status < 300) ||
+ status === 400 ||
+ status === 409
+ );
+ }
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting regex:', error);
+ throw error;
+ }
};
export const getFormats = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/format`);
- return response.data;
- } catch (error) {
- console.error('Error fetching formats:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/format`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching formats:', error);
+ throw error;
+ }
};
export const saveFormat = async format => {
- try {
- const response = await axios.post(`${API_BASE_URL}/format`, format);
- return response.data;
- } catch (error) {
- console.error('Error saving format:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/format`, format);
+ return response.data;
+ } catch (error) {
+ console.error('Error saving format:', error);
+ throw error;
+ }
};
export const updateFormat = async (id, format) => {
- try {
- const response = await axios.put(`${API_BASE_URL}/format/${id}`, format);
- return response.data;
- } catch (error) {
- console.error('Error updating format:', error);
- throw error;
- }
+ try {
+ const response = await axios.put(
+ `${API_BASE_URL}/format/${id}`,
+ format
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error updating format:', error);
+ throw error;
+ }
};
export const deleteFormat = async (id, force = false) => {
- try {
- const response = await axios.delete(
- `${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`,
- {
- validateStatus: status => {
- return (
- (status >= 200 && status < 300) || status === 400 || status === 409
- );
- }
- }
- );
- return response.data;
- } catch (error) {
- console.error('Error deleting format:', error);
- throw error;
- }
+ try {
+ const response = await axios.delete(
+ `${API_BASE_URL}/format/${id}${force ? '?force=true' : ''}`,
+ {
+ validateStatus: status => {
+ return (
+ (status >= 200 && status < 300) ||
+ status === 400 ||
+ status === 409
+ );
+ }
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting format:', error);
+ throw error;
+ }
};
export const createRegex101Link = async regexData => {
- try {
- const response = await axios.post(
- `${API_BASE_URL}/regex/regex101`,
- regexData
- );
- return response.data;
- } catch (error) {
- console.error('Error creating regex101 link:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(
+ `${API_BASE_URL}/regex/regex101`,
+ regexData
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error creating regex101 link:', error);
+ throw error;
+ }
};
export const getSettings = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/settings`);
- return response.data;
- } catch (error) {
- console.error('Error fetching settings:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/settings`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching settings:', error);
+ throw error;
+ }
};
export const getGitStatus = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/git/status`);
- return response.data;
- } catch (error) {
- console.error('Error fetching Git status:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/git/status`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching Git status:', error);
+ throw error;
+ }
};
export const getBranches = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/git/branches`);
- return response.data;
- } catch (error) {
- console.error('Error fetching branches:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/git/branches`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching branches:', error);
+ throw error;
+ }
};
export const checkoutBranch = async branchName => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/checkout`, {
- branch: branchName
- });
- return response.data;
- } catch (error) {
- console.error('Error checking out branch:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/checkout`, {
+ branch: branchName
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error checking out branch:', error);
+ throw error;
+ }
};
export const createBranch = async (branchName, baseBranch) => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/branch`, {
- name: branchName,
- base: baseBranch
- });
- return response.data;
- } catch (error) {
- console.error('Error creating branch:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/branch`, {
+ name: branchName,
+ base: baseBranch
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error creating branch:', error);
+ throw error;
+ }
};
export const deleteBranch = async branchName => {
- try {
- const response = await axios.delete(
- `${API_BASE_URL}/git/branch/${branchName}`
- );
- return response.data;
- } catch (error) {
- console.error('Error deleting branch:', error);
- throw error;
- }
+ try {
+ const response = await axios.delete(
+ `${API_BASE_URL}/git/branch/${branchName}`
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting branch:', error);
+ throw error;
+ }
};
export const addFiles = async files => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/stage`, {files});
- return response.data;
- } catch (error) {
- console.error('Error staging files:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/stage`, {files});
+ return response.data;
+ } catch (error) {
+ console.error('Error staging files:', error);
+ throw error;
+ }
};
export const pushFiles = async (files, commitMessage) => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/push`, {
- files,
- commit_message: commitMessage
- });
- return response.data;
- } catch (error) {
- console.error('Error pushing files:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/push`, {
+ files,
+ commit_message: commitMessage
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error pushing files:', error);
+ throw error;
+ }
};
export const revertFile = async filePath => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/revert`, {
- file_path: filePath
- });
- return response.data;
- } catch (error) {
- console.error('Error reverting file:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/revert`, {
+ file_path: filePath
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error reverting file:', error);
+ throw error;
+ }
};
export const revertAll = async () => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/revert-all`);
- return response.data;
- } catch (error) {
- console.error('Error reverting all changes:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/revert-all`);
+ return response.data;
+ } catch (error) {
+ console.error('Error reverting all changes:', error);
+ throw error;
+ }
};
export const deleteFile = async filePath => {
- try {
- const response = await axios.delete(`${API_BASE_URL}/git/file`, {
- data: {file_path: filePath}
- });
- return response.data;
- } catch (error) {
- console.error('Error deleting file:', error);
- return {success: false, error: 'Error deleting file'};
- }
+ try {
+ const response = await axios.delete(`${API_BASE_URL}/git/file`, {
+ data: {file_path: filePath}
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting file:', error);
+ return {success: false, error: 'Error deleting file'};
+ }
};
export const pullBranch = async branchName => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/pull`, {
- branch: branchName
- });
- return response.data;
- } catch (error) {
- console.error('Error pulling branch:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/pull`, {
+ branch: branchName
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error pulling branch:', error);
+ throw error;
+ }
};
export const getDiff = async filePath => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/diff`, {
- file_path: filePath
- });
- return response.data;
- } catch (error) {
- console.error('Error fetching diff:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/diff`, {
+ file_path: filePath
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching diff:', error);
+ throw error;
+ }
};
export const cloneRepo = async gitRepo => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/clone`, {
- gitRepo
- });
- return response.data;
- } catch (error) {
- console.error('Error cloning repository:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/clone`, {
+ gitRepo
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error cloning repository:', error);
+ throw error;
+ }
};
export const getProfiles = async () => {
- try {
- const response = await axios.get(`${API_BASE_URL}/profile`);
- return response.data;
- } catch (error) {
- console.error('Error fetching profiles:', error);
- throw error;
- }
+ try {
+ const response = await axios.get(`${API_BASE_URL}/profile`);
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching profiles:', error);
+ throw error;
+ }
};
export const saveProfile = async profile => {
- try {
- const response = await axios.post(`${API_BASE_URL}/profile`, profile);
- return response.data;
- } catch (error) {
- console.error('Error saving profile:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/profile`, profile);
+ return response.data;
+ } catch (error) {
+ console.error('Error saving profile:', error);
+ throw error;
+ }
};
export const updateProfile = async (id, profile) => {
- try {
- const response = await axios.put(`${API_BASE_URL}/profile/${id}`, profile);
- return response.data;
- } catch (error) {
- console.error('Error updating profile:', error);
- throw error;
- }
+ try {
+ const response = await axios.put(
+ `${API_BASE_URL}/profile/${id}`,
+ profile
+ );
+ return response.data;
+ } catch (error) {
+ console.error('Error updating profile:', error);
+ throw error;
+ }
};
export const deleteProfile = async id => {
- try {
- const response = await axios.delete(`${API_BASE_URL}/profile/${id}`);
- return response.data;
- } catch (error) {
- console.error('Error deleting profile:', error);
- throw error;
- }
+ try {
+ const response = await axios.delete(`${API_BASE_URL}/profile/${id}`);
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting profile:', error);
+ throw error;
+ }
};
export const unlinkRepo = async (removeFiles = false) => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/unlink`, {
- removeFiles
- });
- return response.data;
- } catch (error) {
- console.error('Error unlinking repository:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/unlink`, {
+ removeFiles
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error unlinking repository:', error);
+ throw error;
+ }
};
export const pushBranchToRemote = async branchName => {
- try {
- const response = await axios.post(`${API_BASE_URL}/git/branch/push`, {
- branch: branchName
- });
- return response.data;
- } catch (error) {
- console.error('Error pushing branch to remote:', error);
- throw error;
- }
+ try {
+ const response = await axios.post(`${API_BASE_URL}/git/branch/push`, {
+ branch: branchName
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Error pushing branch to remote:', error);
+ throw error;
+ }
+};
+
+export const checkDevMode = async () => {
+ try {
+ const response = await axios.get(`${API_BASE_URL}/git/dev`);
+ return response.data;
+ } catch (error) {
+ console.error('Error checking dev mode:', error);
+ throw error;
+ }
};
diff --git a/frontend/src/components/settings/CommitSection.jsx b/frontend/src/components/settings/CommitSection.jsx
index cdb0d81..100ef1c 100644
--- a/frontend/src/components/settings/CommitSection.jsx
+++ b/frontend/src/components/settings/CommitSection.jsx
@@ -6,7 +6,8 @@ const CommitSection = ({
commitMessage,
setCommitMessage,
hasIncomingChanges,
- funMessage
+ funMessage,
+ isDevMode
}) => {
const hasUnstagedChanges = status.outgoing_changes.some(
change => !change.staged || (change.staged && change.modified)
@@ -18,24 +19,43 @@ const CommitSection = ({
return (
- {hasAnyChanges || hasIncomingChanges ? (
+ {isDevMode ? (
<>
- {hasStagedChanges && (
+ {hasAnyChanges || hasIncomingChanges ? (
<>
-
- Commit Message:
-
-
);
diff --git a/frontend/src/components/settings/SettingsPage.jsx b/frontend/src/components/settings/SettingsPage.jsx
index 7bb9b49..127eecd 100644
--- a/frontend/src/components/settings/SettingsPage.jsx
+++ b/frontend/src/components/settings/SettingsPage.jsx
@@ -1,34 +1,35 @@
import React, {useState, useEffect} from 'react';
import {
- getSettings,
- getGitStatus,
- addFiles,
- pushFiles,
- revertFile,
- pullBranch,
- getDiff,
- unlinkRepo
+ getSettings,
+ getGitStatus,
+ addFiles,
+ pushFiles,
+ revertFile,
+ pullBranch,
+ getDiff,
+ unlinkRepo,
+ checkDevMode
} from '../../api/api';
import UnlinkModal from './UnlinkModal';
import SettingsBranchModal from './SettingsBranchModal';
import {
- FileText,
- Code,
- AlertCircle,
- Plus,
- MinusCircle,
- Edit,
- GitBranch,
- Loader,
- Eye,
- RotateCcw,
- Download,
- ArrowDown,
- ArrowUp,
- CheckCircle,
- File,
- Settings,
- Unlink
+ FileText,
+ Code,
+ AlertCircle,
+ Plus,
+ MinusCircle,
+ Edit,
+ GitBranch,
+ Loader,
+ Eye,
+ RotateCcw,
+ Download,
+ ArrowDown,
+ ArrowUp,
+ CheckCircle,
+ File,
+ Settings,
+ Unlink
} from 'lucide-react';
import Alert from '../ui/Alert';
import CommitSection from './CommitSection';
@@ -37,725 +38,854 @@ import DiffModal from './DiffModal';
import LinkRepoModal from './LinkRepoModal';
const SettingsPage = () => {
- const [settings, setSettings] = useState(null);
- const [status, setStatus] = useState(null);
- const [showModal, setShowModal] = useState(false);
- const [showBranchModal, setShowBranchModal] = useState(false);
- const [loadingAction, setLoadingAction] = useState('');
- const [loadingStatus, setLoadingStatus] = useState(true);
- const [loadingMessage, setLoadingMessage] = useState('');
- const [commitMessage, setCommitMessage] = useState('');
- const [showUnlinkModal, setShowUnlinkModal] = useState(false);
- const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]);
- const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]);
- const [showDiffModal, setShowDiffModal] = useState(false);
- const [diffContent, setDiffContent] = useState('');
- const [currentChange, setCurrentChange] = useState(null);
- const [loadingDiff, setLoadingDiff] = useState(false);
- const [selectionType, setSelectionType] = useState(null);
- const [funMessage, setFunMessage] = useState('');
- const [showLinkModal, setShowLinkModal] = useState(false);
- const [sortConfig, setSortConfig] = useState({
- key: 'type',
- direction: 'descending'
- });
-
- useEffect(() => {
- fetchSettings();
- }, []);
-
- const fetchSettings = async () => {
- try {
- const fetchedSettings = await getSettings();
- setSettings(fetchedSettings);
- if (fetchedSettings) {
- await fetchGitStatus();
- }
- } catch (error) {
- console.error('Error fetching settings:', error);
- }
- };
-
- const sortedChanges = changes => {
- if (!sortConfig.key) return changes;
-
- return [...changes].sort((a, b) => {
- if (a[sortConfig.key] < b[sortConfig.key]) {
- return sortConfig.direction === 'ascending' ? -1 : 1;
- }
- if (a[sortConfig.key] > b[sortConfig.key]) {
- return sortConfig.direction === 'ascending' ? 1 : -1;
- }
- return 0;
+ const [settings, setSettings] = useState(null);
+ const [status, setStatus] = useState(null);
+ const [isDevMode, setIsDevMode] = useState(false);
+ const [showModal, setShowModal] = useState(false);
+ const [showBranchModal, setShowBranchModal] = useState(false);
+ const [loadingAction, setLoadingAction] = useState('');
+ const [loadingStatus, setLoadingStatus] = useState(true);
+ const [loadingMessage, setLoadingMessage] = useState('');
+ const [commitMessage, setCommitMessage] = useState('');
+ const [showUnlinkModal, setShowUnlinkModal] = useState(false);
+ const [selectedIncomingChanges, setSelectedIncomingChanges] = useState([]);
+ const [selectedOutgoingChanges, setSelectedOutgoingChanges] = useState([]);
+ const [showDiffModal, setShowDiffModal] = useState(false);
+ const [diffContent, setDiffContent] = useState('');
+ const [currentChange, setCurrentChange] = useState(null);
+ const [loadingDiff, setLoadingDiff] = useState(false);
+ const [selectionType, setSelectionType] = useState(null);
+ const [funMessage, setFunMessage] = useState('');
+ const [showLinkModal, setShowLinkModal] = useState(false);
+ const [sortConfig, setSortConfig] = useState({
+ key: 'type',
+ direction: 'descending'
});
- };
- const requestSort = key => {
- let direction = 'ascending';
- if (sortConfig.key === key && sortConfig.direction === 'ascending') {
- direction = 'descending';
- }
- setSortConfig({key, direction});
- };
+ useEffect(() => {
+ fetchSettings();
+ checkDevModeStatus();
+ }, []);
- const SortableHeader = ({children, sortKey}) => {
- const isSorted = sortConfig.key === sortKey;
- return (
- requestSort(sortKey)}>
-
- {children}
- {isSorted &&
- (sortConfig.direction === 'ascending' ? (
-
- ) : (
-
- ))}
-
- |
- );
- };
+ const checkDevModeStatus = async () => {
+ try {
+ const response = await checkDevMode();
+ setIsDevMode(response.devMode);
+ } catch (error) {
+ console.error('Error checking dev mode:', error);
+ setIsDevMode(false);
+ }
+ };
- const fetchGitStatus = async () => {
- setLoadingStatus(true);
- setLoadingMessage(getRandomLoadingMessage());
- setFunMessage(getRandomFunMessage());
- try {
- const result = await getGitStatus();
- console.log('================ Git Status Response ================');
- console.log(JSON.stringify(result, null, 2));
- console.log('======================================================');
+ const fetchSettings = async () => {
+ try {
+ const fetchedSettings = await getSettings();
+ setSettings(fetchedSettings);
+ if (fetchedSettings) {
+ await fetchGitStatus();
+ }
+ } catch (error) {
+ console.error('Error fetching settings:', error);
+ }
+ };
- if (result.success) {
- setStatus({
- ...result.data,
- outgoing_changes: Array.isArray(result.data.outgoing_changes)
- ? result.data.outgoing_changes
- : [],
- incoming_changes: Array.isArray(result.data.incoming_changes)
- ? result.data.incoming_changes
- : []
+ const sortedChanges = changes => {
+ if (!sortConfig.key) return changes;
+
+ return [...changes].sort((a, b) => {
+ if (a[sortConfig.key] < b[sortConfig.key]) {
+ return sortConfig.direction === 'ascending' ? -1 : 1;
+ }
+ if (a[sortConfig.key] > b[sortConfig.key]) {
+ return sortConfig.direction === 'ascending' ? 1 : -1;
+ }
+ return 0;
});
- }
- } catch (error) {
- console.error('Error fetching Git status:', error);
- Alert.error('Failed to fetch Git status');
- } finally {
- setLoadingStatus(false);
- }
- };
+ };
- const getRandomFunMessage = () => {
- const funMessages = [
- 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐งต๐',
- 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐',
- "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐ง",
- 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐พ',
- "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐ช()",
- 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐๐ค',
- "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐ข๐",
- "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐งนโจ",
- "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐ค๐งฉ",
- 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ฌ๐'
- ];
- return funMessages[Math.floor(Math.random() * funMessages.length)];
- };
-
- const renderChangeTable = (changes, title, icon, isIncoming) => (
-
-
- {icon}
-
- {title} ({changes.length})
-
-
-
-
- );
-
- const getStageButtonTooltip = () => {
- if (selectionType === 'staged') {
- return 'These files are already staged';
- }
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to stage';
- }
- return 'Stage selected files';
- };
-
- const getCommitButtonTooltip = () => {
- if (selectionType === 'unstaged') {
- return 'You can only commit staged files';
- }
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to commit';
- }
- if (!commitMessage.trim()) {
- return 'Enter a commit message';
- }
- return 'Commit selected files';
- };
-
- const getRevertButtonTooltip = () => {
- if (selectedOutgoingChanges.length === 0) {
- return 'Select files to revert';
- }
- return 'Revert selected files';
- };
-
- const handleViewDiff = async change => {
- setLoadingDiff(true);
- try {
- const response = await getDiff(change.file_path);
- console.log('Diff response:', response); // Add this line to log the response
- if (response.success) {
- console.log('Diff content:', response.diff); // Add this line to log the diff content
- setDiffContent(response.diff);
- setCurrentChange(change);
- setShowDiffModal(true);
- } else {
- Alert.error(response.error);
- }
- } catch (error) {
- Alert.error('An unexpected error occurred while fetching the diff.');
- console.error('Error fetching diff:', error);
- } finally {
- setLoadingDiff(false);
- setLoadingAction('');
- }
- };
-
- const handleStageSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to stage.');
- return;
- }
-
- setLoadingAction('stage_selected');
- try {
- const response = await addFiles(selectedOutgoingChanges);
- if (response.success) {
- await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after staging
- Alert.success(response.message);
- } else {
- Alert.error(response.error);
- }
- } catch (error) {
- Alert.error('An unexpected error occurred while staging changes.');
- console.error('Error staging changes:', error);
- } finally {
- setLoadingAction('');
- }
- };
-
- const handleCommitSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to commit.');
- return;
- }
-
- if (!commitMessage.trim()) {
- Alert.warning('Please enter a commit message.');
- return;
- }
-
- setLoadingAction('commit_selected');
- try {
- const response = await pushFiles(selectedOutgoingChanges, commitMessage);
- if (response.success) {
- await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after committing
- setCommitMessage('');
- Alert.success(response.message);
- } else {
- Alert.error(response.error);
- }
- } catch (error) {
- Alert.error('An unexpected error occurred while committing changes.');
- console.error('Error committing changes:', error);
- } finally {
- setLoadingAction('');
- }
- };
-
- const handleRevertSelectedChanges = async () => {
- if (selectedOutgoingChanges.length === 0) {
- Alert.warning('Please select at least one change to revert.');
- return;
- }
-
- setLoadingAction('revert_selected');
- try {
- const response = await Promise.all(
- selectedOutgoingChanges.map(filePath => revertFile(filePath))
- );
- const allSuccessful = response.every(res => res.success);
- if (allSuccessful) {
- await fetchGitStatus();
- setSelectedOutgoingChanges([]); // Clear the selected changes after reverting
- Alert.success('Selected changes have been reverted successfully.');
- } else {
- Alert.error('Some changes could not be reverted. Please try again.');
- }
- } catch (error) {
- Alert.error('An unexpected error occurred while reverting changes.');
- console.error('Error reverting changes:', error);
- } finally {
- setLoadingAction('');
- }
- };
-
- const handlePullSelectedChanges = async () => {
- if (selectedIncomingChanges.length === 0) {
- Alert.warning('Please select at least one change to pull.');
- return;
- }
-
- setLoadingAction('pull_changes');
- try {
- // You would need to update your backend to handle pulling specific files
- const response = await pullBranch(status.branch, selectedIncomingChanges);
- if (response.success) {
- await fetchGitStatus();
- setSelectedIncomingChanges([]); // Clear the selected changes after pulling
- Alert.success(response.message);
- } else {
- Alert.error(response.error);
- }
- } catch (error) {
- Alert.error('An unexpected error occurred while pulling changes.');
- console.error('Error pulling changes:', error);
- } finally {
- setLoadingAction('');
- }
- };
-
- const handleSelectChange = (filePath, isIncoming) => {
- if (isIncoming) {
- setSelectedIncomingChanges(prevSelected => {
- if (prevSelected.includes(filePath)) {
- return prevSelected.filter(path => path !== filePath);
- } else {
- return [...prevSelected, filePath];
+ const requestSort = key => {
+ let direction = 'ascending';
+ if (sortConfig.key === key && sortConfig.direction === 'ascending') {
+ direction = 'descending';
}
- });
- } else {
- const change = status.outgoing_changes.find(
- c => c.file_path === filePath
- );
- const isStaged = change.staged;
+ setSortConfig({key, direction});
+ };
- setSelectedOutgoingChanges(prevSelected => {
- if (prevSelected.includes(filePath)) {
- const newSelection = prevSelected.filter(path => path !== filePath);
- if (newSelection.length === 0) setSelectionType(null);
- return newSelection;
- } else {
- if (
- prevSelected.length === 0 ||
- (isStaged && selectionType === 'staged') ||
- (!isStaged && selectionType === 'unstaged')
- ) {
- setSelectionType(isStaged ? 'staged' : 'unstaged');
- return [...prevSelected, filePath];
- } else {
- return prevSelected;
- }
+ const SortableHeader = ({children, sortKey}) => {
+ const isSorted = sortConfig.key === sortKey;
+ return (
+ requestSort(sortKey)}>
+
+ {children}
+ {isSorted &&
+ (sortConfig.direction === 'ascending' ? (
+
+ ) : (
+
+ ))}
+
+ |
+ );
+ };
+
+ const fetchGitStatus = async () => {
+ setLoadingStatus(true);
+ setLoadingMessage(getRandomLoadingMessage());
+ setFunMessage(getRandomFunMessage());
+ try {
+ const result = await getGitStatus();
+ console.log(
+ '================ Git Status Response ================'
+ );
+ console.log(JSON.stringify(result, null, 2));
+ console.log(
+ '======================================================'
+ );
+
+ if (result.success) {
+ setStatus({
+ ...result.data,
+ outgoing_changes: Array.isArray(
+ result.data.outgoing_changes
+ )
+ ? result.data.outgoing_changes
+ : [],
+ incoming_changes: Array.isArray(
+ result.data.incoming_changes
+ )
+ ? result.data.incoming_changes
+ : []
+ });
+ }
+ } catch (error) {
+ console.error('Error fetching Git status:', error);
+ Alert.error('Failed to fetch Git status');
+ } finally {
+ setLoadingStatus(false);
}
- });
- }
- };
+ };
- const loadingMessages = [
- "Checking for changes... don't blink!",
- 'Syncing with the mothership...',
- 'Peeking under the hood...',
- 'Counting bits and bytes...',
- 'Scanning for modifications...',
- 'Looking for new stuff...',
- 'Comparing local and remote...',
- "Checking your project's pulse...",
- "Analyzing your code's mood...",
- "Reading the project's diary..."
- ];
+ const getRandomFunMessage = () => {
+ const funMessages = [
+ 'No changes detected. Your regex is so precise, it could find a needle in a haystack... made of needles. ๐งต๐',
+ 'All quiet on the commit front. Your custom formats are so perfect, even perfectionists are jealous. ๐',
+ "No updates needed. Your media automation is running so smoothly, it's making butter jealous. ๐ง",
+ 'Zero modifications. Your torrent setup is seeding so efficiently, farmers are asking for advice. ๐พ',
+ "No edits required. Your regex fu is so strong, it's bench-pressing parentheses for fun. ๐ช()",
+ 'Unchanged status. Your Plex library is so well-organized, librarians are taking notes. ๐๐ค',
+ "No alterations found. Your file naming scheme is so consistent, it's bringing tears to OCD eyes. ๐ข๐",
+ "All systems nominal. Your download queue is so orderly, it's making Marie Kondo question her career. ๐งนโจ",
+ "No revisions necessary. Your automation scripts are so smart, they're solving captchas for fun. ๐ค๐งฉ",
+ 'Steady as she goes. Your media collection is so complete, Netflix is asking you for recommendations. ๐ฌ๐'
+ ];
+ return funMessages[Math.floor(Math.random() * funMessages.length)];
+ };
- const getRandomLoadingMessage = () => {
- return loadingMessages[Math.floor(Math.random() * loadingMessages.length)];
- };
-
- const getStatusIcon = status => {
- switch (status) {
- case 'Untracked':
- return ;
- case 'Staged (New)':
- return ;
- case 'Staged (Modified)':
- case 'Modified':
- return ;
- case 'Deleted':
- return ;
- case 'Deleted (Staged)':
- return ;
- case 'Renamed':
- return ;
- default:
- return ;
- }
- };
-
- const getTypeIcon = type => {
- switch (type) {
- case 'Regex Pattern':
- return ;
- case 'Custom Format':
- return ;
- case 'Quality Profile':
- return ;
- default:
- return ;
- }
- };
-
- const handleLinkRepo = async () => {
- setLoadingAction('');
- setShowLinkModal(false);
- await fetchSettings();
- };
-
- const handleUnlinkRepo = async removeFiles => {
- setLoadingAction('unlink_repo');
- try {
- const response = await unlinkRepo(removeFiles);
- if (response.success) {
- setSettings(null);
- setStatus(null);
- Alert.success('Repository unlinked successfully');
- setShowUnlinkModal(false); // Close the modal after unlinking
- } else {
- Alert.error(response.error || 'Failed to unlink repository');
- }
- } catch (error) {
- Alert.error(
- 'An unexpected error occurred while unlinking the repository'
- );
- console.error('Error unlinking repository:', error);
- } finally {
- setLoadingAction('');
- }
- };
-
- return (
-
-
- Git Repository Settings
-
- {!settings && (
-
- )}
- {settings && (
-
-
-
- Connected Repository
-
-
-
- {settings.gitRepo}
-
-
-
-
-
-
-
-
-
-
-
- Git Status
-
- {loadingStatus ? (
-
-
-
- {loadingMessage}
+ const renderChangeTable = (changes, title, icon, isIncoming) => (
+
+
+ {icon}
+
+ {isIncoming
+ ? title
+ : isDevMode
+ ? 'Outgoing Changes'
+ : 'Local Changes'}{' '}
+ ({changes.length})
-
- ) : (
- status && (
- <>
-
-
-
-
- Current Branch: {status.branch}
-
-
-
-
-
- {status.incoming_changes.length > 0 &&
- renderChangeTable(
- status.incoming_changes,
- 'Incoming Changes',
- ,
- true
- )}
- {status.outgoing_changes.length > 0 &&
- renderChangeTable(
- status.outgoing_changes,
- 'Outgoing Changes',
- ,
- false
- )}
-
- 0}
- funMessage={funMessage}
- />
-
- {/* Buttons Below Commit Section */}
-
- {/* Conditionally render Stage button */}
- {selectedOutgoingChanges.length > 0 &&
- selectionType !== 'staged' && (
-
-
-
- )}
-
- {/* Stage button */}
- {selectedOutgoingChanges.length > 0 &&
- selectionType !== 'staged' && (
-
-
-
- )}
-
- {/* Commit button */}
- {selectedOutgoingChanges.length > 0 &&
- commitMessage.trim() &&
- selectionType !== 'unstaged' && (
-
-
-
- )}
-
- {/* Revert button */}
- {selectedOutgoingChanges.length > 0 && (
-
-
-
- )}
-
-
- {/* Pull button (always enabled) */}
- {selectedIncomingChanges.length > 0 && (
-
-
-
- )}
-
- >
- )
- )}
-
+
+
- )}
- {settings && status && (
-
setShowBranchModal(false)}
- repoUrl={settings.gitRepo}
- currentBranch={status.branch}
- onBranchChange={fetchGitStatus}
- />
- )}
- {showDiffModal && currentChange && (
- setShowDiffModal(false)}
- diffContent={diffContent}
- type={currentChange.type}
- name={currentChange.name}
- commitMessage={currentChange.commit_message}
- />
- )}
- setShowLinkModal(false)}
- onSubmit={handleLinkRepo}
- />
- setShowUnlinkModal(false)}
- onSubmit={handleUnlinkRepo}
- />
-
- );
+ );
+
+ const getStageButtonTooltip = () => {
+ if (selectionType === 'staged') {
+ return 'These files are already staged';
+ }
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to stage';
+ }
+ return 'Stage selected files';
+ };
+
+ const getCommitButtonTooltip = () => {
+ if (selectionType === 'unstaged') {
+ return 'You can only commit staged files';
+ }
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to commit';
+ }
+ if (!commitMessage.trim()) {
+ return 'Enter a commit message';
+ }
+ return 'Commit selected files';
+ };
+
+ const getRevertButtonTooltip = () => {
+ if (selectedOutgoingChanges.length === 0) {
+ return 'Select files to revert';
+ }
+ return 'Revert selected files';
+ };
+
+ const handleViewDiff = async change => {
+ setLoadingDiff(true);
+ try {
+ const response = await getDiff(change.file_path);
+ console.log('Diff response:', response); // Add this line to log the response
+ if (response.success) {
+ console.log('Diff content:', response.diff); // Add this line to log the diff content
+ setDiffContent(response.diff);
+ setCurrentChange(change);
+ setShowDiffModal(true);
+ } else {
+ Alert.error(response.error);
+ }
+ } catch (error) {
+ Alert.error(
+ 'An unexpected error occurred while fetching the diff.'
+ );
+ console.error('Error fetching diff:', error);
+ } finally {
+ setLoadingDiff(false);
+ setLoadingAction('');
+ }
+ };
+
+ const handleStageSelectedChanges = async () => {
+ if (selectedOutgoingChanges.length === 0) {
+ Alert.warning('Please select at least one change to stage.');
+ return;
+ }
+
+ setLoadingAction('stage_selected');
+ try {
+ const response = await addFiles(selectedOutgoingChanges);
+ if (response.success) {
+ await fetchGitStatus();
+ setSelectedOutgoingChanges([]); // Clear the selected changes after staging
+ Alert.success(response.message);
+ } else {
+ Alert.error(response.error);
+ }
+ } catch (error) {
+ Alert.error('An unexpected error occurred while staging changes.');
+ console.error('Error staging changes:', error);
+ } finally {
+ setLoadingAction('');
+ }
+ };
+
+ const handleCommitSelectedChanges = async () => {
+ if (selectedOutgoingChanges.length === 0) {
+ Alert.warning('Please select at least one change to commit.');
+ return;
+ }
+
+ if (!commitMessage.trim()) {
+ Alert.warning('Please enter a commit message.');
+ return;
+ }
+
+ setLoadingAction('commit_selected');
+ try {
+ const response = await pushFiles(
+ selectedOutgoingChanges,
+ commitMessage
+ );
+ if (response.success) {
+ await fetchGitStatus();
+ setSelectedOutgoingChanges([]); // Clear the selected changes after committing
+ setCommitMessage('');
+ Alert.success(response.message);
+ } else {
+ Alert.error(response.error);
+ }
+ } catch (error) {
+ Alert.error(
+ 'An unexpected error occurred while committing changes.'
+ );
+ console.error('Error committing changes:', error);
+ } finally {
+ setLoadingAction('');
+ }
+ };
+
+ const handleRevertSelectedChanges = async () => {
+ if (selectedOutgoingChanges.length === 0) {
+ Alert.warning('Please select at least one change to revert.');
+ return;
+ }
+
+ setLoadingAction('revert_selected');
+ try {
+ const response = await Promise.all(
+ selectedOutgoingChanges.map(filePath => revertFile(filePath))
+ );
+ const allSuccessful = response.every(res => res.success);
+ if (allSuccessful) {
+ await fetchGitStatus();
+ setSelectedOutgoingChanges([]); // Clear the selected changes after reverting
+ Alert.success(
+ 'Selected changes have been reverted successfully.'
+ );
+ } else {
+ Alert.error(
+ 'Some changes could not be reverted. Please try again.'
+ );
+ }
+ } catch (error) {
+ Alert.error(
+ 'An unexpected error occurred while reverting changes.'
+ );
+ console.error('Error reverting changes:', error);
+ } finally {
+ setLoadingAction('');
+ }
+ };
+
+ const handlePullSelectedChanges = async () => {
+ if (selectedIncomingChanges.length === 0) {
+ Alert.warning('Please select at least one change to pull.');
+ return;
+ }
+
+ setLoadingAction('pull_changes');
+ try {
+ // You would need to update your backend to handle pulling specific files
+ const response = await pullBranch(
+ status.branch,
+ selectedIncomingChanges
+ );
+ if (response.success) {
+ await fetchGitStatus();
+ setSelectedIncomingChanges([]); // Clear the selected changes after pulling
+ Alert.success(response.message);
+ } else {
+ Alert.error(response.error);
+ }
+ } catch (error) {
+ Alert.error('An unexpected error occurred while pulling changes.');
+ console.error('Error pulling changes:', error);
+ } finally {
+ setLoadingAction('');
+ }
+ };
+
+ const handleSelectChange = (filePath, isIncoming) => {
+ if (isIncoming) {
+ setSelectedIncomingChanges(prevSelected => {
+ if (prevSelected.includes(filePath)) {
+ return prevSelected.filter(path => path !== filePath);
+ } else {
+ return [...prevSelected, filePath];
+ }
+ });
+ } else {
+ const change = status.outgoing_changes.find(
+ c => c.file_path === filePath
+ );
+ const isStaged = change.staged;
+
+ setSelectedOutgoingChanges(prevSelected => {
+ if (prevSelected.includes(filePath)) {
+ const newSelection = prevSelected.filter(
+ path => path !== filePath
+ );
+ if (newSelection.length === 0) setSelectionType(null);
+ return newSelection;
+ } else {
+ if (
+ prevSelected.length === 0 ||
+ (isStaged && selectionType === 'staged') ||
+ (!isStaged && selectionType === 'unstaged')
+ ) {
+ setSelectionType(isStaged ? 'staged' : 'unstaged');
+ return [...prevSelected, filePath];
+ } else {
+ return prevSelected;
+ }
+ }
+ });
+ }
+ };
+
+ const loadingMessages = [
+ "Checking for changes... don't blink!",
+ 'Syncing with the mothership...',
+ 'Peeking under the hood...',
+ 'Counting bits and bytes...',
+ 'Scanning for modifications...',
+ 'Looking for new stuff...',
+ 'Comparing local and remote...',
+ "Checking your project's pulse...",
+ "Analyzing your code's mood...",
+ "Reading the project's diary..."
+ ];
+
+ const getRandomLoadingMessage = () => {
+ return loadingMessages[
+ Math.floor(Math.random() * loadingMessages.length)
+ ];
+ };
+
+ const getStatusIcon = status => {
+ switch (status) {
+ case 'Untracked':
+ return
;
+ case 'Staged (New)':
+ return
;
+ case 'Staged (Modified)':
+ case 'Modified':
+ return
;
+ case 'Deleted':
+ return
;
+ case 'Deleted (Staged)':
+ return
;
+ case 'Renamed':
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ const getTypeIcon = type => {
+ switch (type) {
+ case 'Regex Pattern':
+ return
;
+ case 'Custom Format':
+ return
;
+ case 'Quality Profile':
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ const handleLinkRepo = async () => {
+ setLoadingAction('');
+ setShowLinkModal(false);
+ await fetchSettings();
+ };
+
+ const handleUnlinkRepo = async removeFiles => {
+ setLoadingAction('unlink_repo');
+ try {
+ const response = await unlinkRepo(removeFiles);
+ if (response.success) {
+ setSettings(null);
+ setStatus(null);
+ Alert.success('Repository unlinked successfully');
+ setShowUnlinkModal(false); // Close the modal after unlinking
+ } else {
+ Alert.error(response.error || 'Failed to unlink repository');
+ }
+ } catch (error) {
+ Alert.error(
+ 'An unexpected error occurred while unlinking the repository'
+ );
+ console.error('Error unlinking repository:', error);
+ } finally {
+ setLoadingAction('');
+ }
+ };
+
+ return (
+
+
+ Git Repository Settings
+
+ {!settings && (
+
+ )}
+ {settings && (
+
+
+
+ Connected Repository
+
+
+
+ {settings.gitRepo}
+
+
+
+
+
+
+
+
+
+
+
+ Git Status
+
+ {loadingStatus ? (
+
+
+
+ {loadingMessage}
+
+
+ ) : (
+ status && (
+ <>
+
+
+
+
+ Current Branch: {status.branch}
+
+
+
+
+
+ {status.incoming_changes.length > 0 &&
+ renderChangeTable(
+ status.incoming_changes,
+ 'Incoming Changes',
+
,
+ true
+ )}
+ {status.outgoing_changes.length > 0 &&
+ renderChangeTable(
+ status.outgoing_changes,
+ 'Outgoing Changes',
+
,
+ false
+ )}
+
0
+ }
+ funMessage={funMessage}
+ isDevMode={isDevMode}
+ />
+ {/* Buttons Below Commit Section */}
+
+ {isDevMode && (
+ <>
+ {/* Stage button */}
+ {selectedOutgoingChanges.length >
+ 0 &&
+ selectionType !==
+ 'staged' && (
+
+
+
+ )}
+
+ {/* Commit button */}
+ {selectedOutgoingChanges.length >
+ 0 &&
+ commitMessage.trim() &&
+ selectionType !==
+ 'unstaged' && (
+
+
+
+ )}
+ >
+ )}
+
+ {/* Revert button (moved outside isDevMode check) */}
+ {selectedOutgoingChanges.length > 0 && (
+
+
+
+ )}
+
+ {/* Pull button (always enabled) */}
+ {selectedIncomingChanges.length > 0 && (
+
+
+
+ )}
+
+ >
+ )
+ )}
+
+
+ )}
+ {settings && status && (
+
setShowBranchModal(false)}
+ repoUrl={settings.gitRepo}
+ currentBranch={status.branch}
+ onBranchChange={fetchGitStatus}
+ />
+ )}
+ {showDiffModal && currentChange && (
+ setShowDiffModal(false)}
+ diffContent={diffContent}
+ type={currentChange.type}
+ name={currentChange.name}
+ commitMessage={currentChange.commit_message}
+ />
+ )}
+ setShowLinkModal(false)}
+ onSubmit={handleLinkRepo}
+ />
+ setShowUnlinkModal(false)}
+ onSubmit={handleUnlinkRepo}
+ />
+
+ );
};
export default SettingsPage;