('idle')
+ const [errorMsg, setErrorMsg] = useState('')
+
+ async function handleSave(e: React.FormEvent) {
+ e.preventDefault()
+ setLastAction('save')
+ setStatus('busy')
+ setErrorMsg('')
+ try {
+ await postJSON(`/project/${projectId}/git-sync`, {
+ body: { remoteUrl },
+ })
+ setStatus('success')
+ } catch (err: any) {
+ setStatus('error')
+ setErrorMsg(err?.data?.error ?? String(err))
+ }
+ }
+
+ async function handlePush() {
+ setLastAction('push')
+ setStatus('busy')
+ setErrorMsg('')
+ try {
+ await postJSON(`/project/${projectId}/git-sync/push`, { body: {} })
+ setStatus('success')
+ } catch (err: any) {
+ setStatus('error')
+ setErrorMsg(err?.data?.error ?? String(err))
+ }
+ }
+
+ const isBusy = status === 'busy'
+
+ return (
+
+
+
+ {t('git_sync')}
+
+
{t('git_sync_description')}
+
+
+
+ {status === 'success' && (
+
+ )}
+ {status === 'error' && (
+
+ )}
+
+ )
+}
diff --git a/services/web/frontend/js/features/integrations-panel/integrations-panel.tsx b/services/web/frontend/js/features/integrations-panel/integrations-panel.tsx
index e4d8dc153e..667b4dcf89 100644
--- a/services/web/frontend/js/features/integrations-panel/integrations-panel.tsx
+++ b/services/web/frontend/js/features/integrations-panel/integrations-panel.tsx
@@ -2,6 +2,8 @@ import { ElementType } from 'react'
import importOverleafModules from '../../../macros/import-overleaf-module.macro'
import { useTranslation } from 'react-i18next'
import RailPanelHeader from '@/features/ide-react/components/rail/rail-panel-header'
+import getMeta from '@/utils/meta'
+import GitSyncWidget from './git-sync-widget'
const integrationPanelComponents = importOverleafModules(
'integrationPanelComponents'
@@ -9,10 +11,12 @@ const integrationPanelComponents = importOverleafModules(
export default function IntegrationsPanel() {
const { t } = useTranslation()
+ const gitSyncEnabled = getMeta('ol-gitSyncEnabled')
return (
+ {gitSyncEnabled && }
{integrationPanelComponents.map(
({ import: { default: Component }, path }) => (
diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts
index ea71ed2358..f797729cfa 100644
--- a/services/web/frontend/js/utils/meta.ts
+++ b/services/web/frontend/js/utils/meta.ts
@@ -136,6 +136,8 @@ export interface Meta {
'ol-galleryTagName': string
'ol-gitBridgeEnabled': boolean
'ol-gitBridgePublicBaseUrl': string
+ 'ol-gitSyncEnabled': boolean
+ 'ol-gitRemote': string
'ol-github': { enabled: boolean; error: boolean }
'ol-groupAuditLogs': []
'ol-groupDomains': []
diff --git a/services/web/frontend/stylesheets/components/integrations-panel.scss b/services/web/frontend/stylesheets/components/integrations-panel.scss
index 9b6510b8ab..86a1ae3458 100644
--- a/services/web/frontend/stylesheets/components/integrations-panel.scss
+++ b/services/web/frontend/stylesheets/components/integrations-panel.scss
@@ -99,3 +99,33 @@
color: var(--integrations-panel-description-color);
margin-bottom: 0;
}
+
+.git-sync-widget {
+ padding: var(--spacing-04);
+ border-bottom: 1px solid var(--integrations-panel-card-border);
+
+ .git-sync-widget-header {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-02);
+ margin-bottom: var(--spacing-02);
+ }
+
+ .git-sync-widget-title {
+ font-size: var(--font-size-02);
+ font-weight: 600;
+ color: var(--integrations-panel-color);
+ }
+
+ .git-sync-widget-description {
+ font-size: var(--font-size-01);
+ color: var(--integrations-panel-description-color);
+ margin-bottom: var(--spacing-04);
+ }
+
+ .git-sync-widget-actions {
+ display: flex;
+ gap: var(--spacing-02);
+ margin-top: var(--spacing-02);
+ }
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 84e2151405..b4a34f27ba 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1043,6 +1043,13 @@
"git_bridge_modal_git_clone_your_project": "Git clone your project by using the link below and a Git authentication token",
"git_bridge_modal_learn_more_about_authentication_tokens": "Learn more about Git integration authentication tokens.",
"git_bridge_modal_read_only": "You have read-only access to this project. This means you can pull from __appName__ but you can’t push any changes you make back to this project.",
+ "git_sync": "Git Sync",
+ "git_sync_description": "Force-push your project files to an external git repository. Include your auth token in the URL (e.g. https://user:token@github.com/org/repo.git).",
+ "git_sync_push_now": "Push now",
+ "git_sync_pushing": "Pushing…",
+ "git_sync_push_success": "Project pushed to git remote successfully.",
+ "git_sync_remote_url": "Remote URL (with auth token)",
+ "git_sync_saved": "Remote URL saved.",
"git_bridge_modal_review_access": "<0>You have review access to this project.0> This means you can pull from __appName__ but you can’t push any changes you make back to this project.",
"git_bridge_modal_see_once": "You’ll only see this token once. To delete it or generate a new one, visit Account settings. For detailed instructions and troubleshooting, read our <0>help page0>.",
"git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account settings. For more support, read our <0>help page0>.",
diff --git a/services/web/locales/fr.json b/services/web/locales/fr.json
index 0d7c764170..f0a34e9eed 100644
--- a/services/web/locales/fr.json
+++ b/services/web/locales/fr.json
@@ -1046,6 +1046,13 @@
"git_bridge_modal_git_clone_your_project": "Git clonez votre projet en utilisant le lien ci-dessous et un jeton d'authentification Git",
"git_bridge_modal_learn_more_about_authentication_tokens": "Apprenez-en davantage sur les jetons d’authentification de l’intégration Git.",
"git_bridge_modal_read_only": "Vous disposez d'un accès en lecture seule à ce projet. Cela signifie que vous pouvez extraire des données de __appName__, mais que vous ne pouvez pas transférer les modifications que vous apportez à ce projet.",
+ "git_sync": "Synchronisation Git",
+ "git_sync_description": "Pousse de force les fichiers du projet vers un dépôt git externe. Incluez votre jeton d'authentification dans l'URL (ex. https://user:token@github.com/org/repo.git).",
+ "git_sync_push_now": "Pousser maintenant",
+ "git_sync_pushing": "Envoi en cours…",
+ "git_sync_push_success": "Projet envoyé vers le dépôt git avec succès.",
+ "git_sync_remote_url": "URL distante (avec jeton d'auth)",
+ "git_sync_saved": "URL distante sauvegardée.",
"git_bridge_modal_review_access": "<0>You have review access to this project.0> Cela signifie que vous pouvez extraire de __appName__ mais vous ne pouvez pas repousser les modifications que vous apportez à ce projet.",
"git_bridge_modal_see_once": "Vous n’allez voir ce jeton qu’une seule fois. Pour l’effacer ou pour en générer un nouveau, allez dans Paramètres du compte. Pour des instructions détaillées ou pour résoudre des problèmes, lisez notre <0>page d’aide0>.",
"git_bridge_modal_use_previous_token": "Si un mot de passe vous est demandé, vous pouvez utiliser un jeton Git précédent ou en générer un nouveau dans Paramètres du compte. Pour plus d’informations, lisez notre <0>page d’aide0>.",