import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, } from "@/components/ui/card"; import { githubApi } from "@/lib/api"; import type { GitHubConfig, MirrorOptions, AdvancedOptions, GiteaConfig, BackupStrategy } from "@/types/config"; import { Input } from "../ui/input"; import { toast } from "sonner"; import { Info, ShieldAlert } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { GitHubMirrorSettings } from "./GitHubMirrorSettings"; import { Separator } from "../ui/separator"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; interface GitHubConfigFormProps { config: GitHubConfig; setConfig: React.Dispatch>; mirrorOptions: MirrorOptions; setMirrorOptions: React.Dispatch>; advancedOptions: AdvancedOptions; setAdvancedOptions: React.Dispatch>; giteaConfig?: GiteaConfig; setGiteaConfig?: React.Dispatch>; onAutoSave?: (githubConfig: GitHubConfig) => Promise; onMirrorOptionsAutoSave?: (mirrorOptions: MirrorOptions) => Promise; onAdvancedOptionsAutoSave?: (advancedOptions: AdvancedOptions) => Promise; onGiteaAutoSave?: (giteaConfig: GiteaConfig) => Promise; isAutoSaving?: boolean; } export function GitHubConfigForm({ config, setConfig, mirrorOptions, setMirrorOptions, advancedOptions, setAdvancedOptions, giteaConfig, setGiteaConfig, onAutoSave, onMirrorOptionsAutoSave, onAdvancedOptionsAutoSave, onGiteaAutoSave, isAutoSaving }: GitHubConfigFormProps) { const [isLoading, setIsLoading] = useState(false); const handleChange = (e: React.ChangeEvent) => { const { name, value, type, checked } = e.target; const newConfig = { ...config, [name]: type === "checkbox" ? checked : value, }; setConfig(newConfig); // Auto-save for all field changes if (onAutoSave) { onAutoSave(newConfig); } }; const testConnection = async () => { if (!config.token) { toast.error("GitHub token is required to test the connection"); return; } setIsLoading(true); try { const result = await githubApi.testConnection(config.token); if (result.success) { toast.success("Successfully connected to GitHub!"); } else { toast.error("Failed to connect to GitHub. Please check your token."); } } catch (error) { toast.error( error instanceof Error ? error.message : "An unknown error occurred" ); } finally { setIsLoading(false); } }; return ( GitHub Configuration {/* Desktop: Show button in header */}

GitHub Token Requirements

You need to create a Classic GitHub PAT Token with the following scopes:

  • repo
  • admin:org

The organization access is required for mirroring organization repositories.

Generate tokens at{" "} github.com/settings/tokens

Required for private repositories, organizations, and starred repositories.

{ setConfig(newConfig); if (onAutoSave) onAutoSave(newConfig); }} onMirrorOptionsChange={(newOptions) => { setMirrorOptions(newOptions); if (onMirrorOptionsAutoSave) onMirrorOptionsAutoSave(newOptions); }} onAdvancedOptionsChange={(newOptions) => { setAdvancedOptions(newOptions); if (onAdvancedOptionsAutoSave) onAdvancedOptionsAutoSave(newOptions); }} /> {giteaConfig && setGiteaConfig && ( <>

Destructive Update Protection BETA

Choose how to handle force-pushes or rewritten upstream history on GitHub.

{([ { value: "disabled", label: "Disabled", desc: "No detection or backups", }, { value: "always", label: "Always Backup", desc: "Snapshot before every sync", }, { value: "on-force-push", label: "Smart", desc: "Backup only on force-push", }, { value: "block-on-force-push", label: "Block & Approve", desc: "Require approval on force-push", }, ] as const).map((opt) => { const isSelected = (giteaConfig.backupStrategy ?? "on-force-push") === opt.value; return ( ); })}
{(giteaConfig.backupStrategy ?? "on-force-push") !== "disabled" && ( <>
{ const newConfig = { ...giteaConfig, backupRetentionCount: Math.max(1, Number.parseInt(e.target.value, 10) || 20), }; setGiteaConfig(newConfig); if (onGiteaAutoSave) onGiteaAutoSave(newConfig); }} className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" />
{ const newConfig = { ...giteaConfig, backupDirectory: e.target.value }; setGiteaConfig(newConfig); if (onGiteaAutoSave) onGiteaAutoSave(newConfig); }} className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" placeholder="data/repo-backups" />
{((giteaConfig.backupStrategy ?? "on-force-push") === "always" || (giteaConfig.backupStrategy ?? "on-force-push") === "on-force-push") && ( )} )}
)} {/* Mobile: Show button at bottom */}
); }