feat: add OrganizationConfiguration component and integrate it into GiteaConfigForm

This commit is contained in:
Arunavo Ray
2025-06-17 13:20:03 +05:30
parent 13d4b03541
commit 047719cde9
3 changed files with 235 additions and 206 deletions

View File

@@ -10,6 +10,7 @@ import { giteaApi } from "@/lib/api";
import type { GiteaConfig, MirrorStrategy } from "@/types/config";
import { toast } from "sonner";
import { OrganizationStrategy } from "./OrganizationStrategy";
import { OrganizationConfiguration } from "./OrganizationConfiguration";
import { Separator } from "../ui/separator";
interface GiteaConfigFormProps {
@@ -205,8 +206,18 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
strategy={mirrorStrategy}
destinationOrg={config.organization}
starredReposOrg={config.starredReposOrg}
visibility={config.visibility}
onStrategyChange={setMirrorStrategy}
githubUsername={githubUsername}
giteaUsername={config.username}
/>
<Separator />
<OrganizationConfiguration
strategy={mirrorStrategy}
destinationOrg={config.organization}
starredReposOrg={config.starredReposOrg}
visibility={config.visibility}
onDestinationOrgChange={(org) => {
const newConfig = { ...config, organization: org };
setConfig(newConfig);
@@ -222,8 +233,6 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
setConfig(newConfig);
if (onAutoSave) onAutoSave(newConfig);
}}
githubUsername={githubUsername}
giteaUsername={config.username}
/>
</CardContent>
</Card>

View File

@@ -0,0 +1,222 @@
import React from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Building2, Star, Globe, Lock, Shield, Info } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { MirrorStrategy, GiteaOrgVisibility } from "@/types/config";
interface OrganizationConfigurationProps {
strategy: MirrorStrategy;
destinationOrg?: string;
starredReposOrg?: string;
visibility: GiteaOrgVisibility;
onDestinationOrgChange: (org: string) => void;
onStarredReposOrgChange: (org: string) => void;
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
}
const visibilityOptions = [
{ value: "public" as GiteaOrgVisibility, label: "Public", icon: Globe, description: "Visible to everyone" },
{ value: "private" as GiteaOrgVisibility, label: "Private", icon: Lock, description: "Visible to members only" },
{ value: "limited" as GiteaOrgVisibility, label: "Limited", icon: Shield, description: "Visible to logged-in users" },
];
export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps> = ({
strategy,
destinationOrg,
starredReposOrg,
visibility,
onDestinationOrgChange,
onStarredReposOrgChange,
onVisibilityChange,
}) => {
return (
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
<Building2 className="h-4 w-4" />
Organization Configuration
</h4>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{strategy === "single-org" ? (
<>
{/* Destination Organization - Left Column */}
<div className="space-y-1">
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
Destination Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>All repositories will be mirrored to this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="destinationOrg"
value={destinationOrg || ""}
onChange={(e) => onDestinationOrgChange(e.target.value)}
placeholder="github-mirrors"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Organization for consolidated repositories
</p>
</div>
{/* Starred Repositories Organization - Right Column */}
<div className="space-y-1">
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
<Star className="h-3.5 w-3.5" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Keep starred repos organized separately
</p>
</div>
</>
) : (
<>
{/* Starred Repositories Organization - Left Column */}
<div className="space-y-1">
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
<Star className="h-3.5 w-3.5" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Keep starred repos organized separately
</p>
</div>
{/* Organization Visibility - Right Column */}
<div className="space-y-2">
<Label className="text-sm font-normal flex items-center gap-2">
Organization Visibility
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Visibility for newly created organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<div className="flex gap-2">
{visibilityOptions.map((option) => {
const Icon = option.icon;
const isSelected = visibility === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => onVisibilityChange(option.value)}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm transition-all",
"border flex-1",
isSelected
? "bg-accent border-accent-foreground/20"
: "bg-background hover:bg-accent/50 border-input"
)}
>
<Icon className="h-3.5 w-3.5" />
<span>{option.label}</span>
</button>
);
})}
</div>
</div>
</>
)}
</div>
{/* Organization Visibility - Full width when single-org is selected */}
{strategy === "single-org" && (
<div className="space-y-2">
<Label className="text-sm font-normal flex items-center gap-2">
Organization Visibility
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Visibility for newly created organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<div className="flex gap-2">
{visibilityOptions.map((option) => {
const Icon = option.icon;
const isSelected = visibility === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => onVisibilityChange(option.value)}
className={cn(
"flex items-center gap-1.5 px-4 py-1.5 rounded-md text-sm transition-all",
"border",
isSelected
? "bg-accent border-accent-foreground/20"
: "bg-background hover:bg-accent/50 border-input"
)}
>
<Icon className="h-3.5 w-3.5" />
<span>{option.label}</span>
</button>
);
})}
</div>
</div>
)}
</div>
);
};

View File

@@ -1,10 +1,7 @@
import React from "react";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Info, GitBranch, FolderTree, Star, Building2, User, Globe, Lock, Shield } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { Info, GitBranch, FolderTree, Star, Building2, User } from "lucide-react";
import {
Tooltip,
TooltipContent,
@@ -17,7 +14,6 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import type { GiteaOrgVisibility } from "@/types/config";
export type MirrorStrategy = "preserve" | "single-org" | "flat-user";
@@ -25,11 +21,7 @@ interface OrganizationStrategyProps {
strategy: MirrorStrategy;
destinationOrg?: string;
starredReposOrg?: string;
visibility: GiteaOrgVisibility;
onStrategyChange: (strategy: MirrorStrategy) => void;
onDestinationOrgChange: (org: string) => void;
onStarredReposOrgChange: (org: string) => void;
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
githubUsername?: string;
giteaUsername?: string;
}
@@ -231,20 +223,10 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
strategy,
destinationOrg,
starredReposOrg,
visibility,
onStrategyChange,
onDestinationOrgChange,
onStarredReposOrgChange,
onVisibilityChange,
githubUsername,
giteaUsername,
}) => {
const visibilityOptions = [
{ value: "public" as GiteaOrgVisibility, label: "Public", icon: Globe, description: "Visible to everyone" },
{ value: "private" as GiteaOrgVisibility, label: "Private", icon: Lock, description: "Visible to members only" },
{ value: "limited" as GiteaOrgVisibility, label: "Limited", icon: Shield, description: "Visible to logged-in users" },
];
return (
<div className="space-y-4">
<div>
@@ -329,190 +311,6 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
})}
</div>
</RadioGroup>
<Separator className="my-4" />
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-3 flex items-center gap-2">
<Building2 className="h-4 w-4" />
Organization Configuration
</h4>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{strategy === "single-org" ? (
<>
{/* Destination Organization - Left Column */}
<div className="space-y-1">
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
Destination Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>All repositories will be mirrored to this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="destinationOrg"
value={destinationOrg || ""}
onChange={(e) => onDestinationOrgChange(e.target.value)}
placeholder="github-mirrors"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Organization for consolidated repositories
</p>
</div>
{/* Starred Repositories Organization - Right Column */}
<div className="space-y-1">
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
<Star className="h-3.5 w-3.5" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Keep starred repos organized separately
</p>
</div>
</>
) : (
<>
{/* Starred Repositories Organization - Left Column */}
<div className="space-y-1">
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
<Star className="h-3.5 w-3.5" />
Starred Repositories Organization
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Starred repositories will be organized separately in this organization</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<Input
id="starredReposOrg"
value={starredReposOrg || ""}
onChange={(e) => onStarredReposOrgChange(e.target.value)}
placeholder="starred"
className=""
/>
<p className="text-xs text-muted-foreground mt-1">
Keep starred repos organized separately
</p>
</div>
{/* Organization Visibility - Right Column */}
<div className="space-y-2">
<Label className="text-sm font-normal flex items-center gap-2">
Organization Visibility
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Visibility for newly created organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<div className="flex gap-2">
{visibilityOptions.map((option) => {
const Icon = option.icon;
const isSelected = visibility === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => onVisibilityChange(option.value)}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm transition-all",
"border flex-1",
isSelected
? "bg-accent border-accent-foreground/20"
: "bg-background hover:bg-accent/50 border-input"
)}
>
<Icon className="h-3.5 w-3.5" />
<span>{option.label}</span>
</button>
);
})}
</div>
</div>
</>
)}
</div>
{/* Organization Visibility - Full width when single-org is selected */}
{strategy === "single-org" && (
<div className="space-y-2">
<Label className="text-sm font-normal flex items-center gap-2">
Organization Visibility
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3.5 w-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>Visibility for newly created organizations</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
<div className="flex gap-2">
{visibilityOptions.map((option) => {
const Icon = option.icon;
const isSelected = visibility === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => onVisibilityChange(option.value)}
className={cn(
"flex items-center gap-1.5 px-4 py-1.5 rounded-md text-sm transition-all",
"border",
isSelected
? "bg-accent border-accent-foreground/20"
: "bg-background hover:bg-accent/50 border-input"
)}
>
<Icon className="h-3.5 w-3.5" />
<span>{option.label}</span>
</button>
);
})}
</div>
</div>
)}
</div>
</div>
);
};