tool-manager.tsx•6.04 kB
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { Eye, EyeOff, Key, Save, Info } from "lucide-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import LoadingSpinner from "@/components/ui/loading-spinner";
interface TokenManagerProps {
onClose: () => void;
}
export default function TokenManager({ onClose }: TokenManagerProps) {
const [newToken, setNewToken] = useState("");
const [showToken, setShowToken] = useState(false);
const { toast } = useToast();
const queryClient = useQueryClient();
// Get current token status
const { data: tokenStatus } = useQuery({
queryKey: ["/api/token/status"],
});
const updateTokenMutation = useMutation({
mutationFn: async (token: string) => {
const response = await apiRequest("PUT", "/api/token/update", { token });
return response.json();
},
onSuccess: () => {
toast({
title: "Success!",
description: "GitHub token updated successfully.",
});
// Invalidate relevant queries
queryClient.invalidateQueries({ queryKey: ["/api/token/status"] });
queryClient.invalidateQueries({ queryKey: ["/api/tools"] });
onClose();
},
onError: (error: any) => {
toast({
title: "Update Failed",
description: error.message || "Failed to update token. Please try again.",
variant: "destructive",
});
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!newToken.trim()) {
toast({
title: "Token Required",
description: "Please enter a new token.",
variant: "destructive",
});
return;
}
if (!newToken.startsWith('ghp_') && !newToken.startsWith('github_pat_')) {
toast({
title: "Invalid Format",
description: "Please enter a valid GitHub personal access token.",
variant: "destructive",
});
return;
}
updateTokenMutation.mutate(newToken);
};
const formatDate = (dateString: string | undefined) => {
if (!dateString) return "Unknown";
return new Date(dateString).toLocaleString();
};
return (
<Dialog open={true} onOpenChange={onClose}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-3">
<div className="w-10 h-10 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg flex items-center justify-center">
<Key className="h-5 w-5 text-white" />
</div>
<div>
<h3 className="text-xl font-semibold">Manage GitHub Token</h3>
<p className="text-sm text-muted-foreground">Update your authentication credentials</p>
</div>
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-github-token">New GitHub Token</Label>
<div className="relative">
<Input
id="new-github-token"
type={showToken ? "text" : "password"}
placeholder="Enter new token..."
value={newToken}
onChange={(e) => setNewToken(e.target.value)}
className="pr-10"
disabled={updateTokenMutation.isPending}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowToken(!showToken)}
disabled={updateTokenMutation.isPending}
>
{showToken ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
</div>
<Card className="bg-muted/50">
<CardContent className="pt-4">
<div className="flex items-center mb-2">
<Info className="h-4 w-4 text-primary mr-2" />
<span className="text-sm font-medium">Current Status</span>
</div>
<div className="space-y-1 text-sm text-muted-foreground">
<div className="flex items-center">
<div className="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
<span>Token active</span>
</div>
</div>
</CardContent>
</Card>
<div className="flex items-center justify-between pt-4">
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button
type="submit"
disabled={updateTokenMutation.isPending}
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
>
{updateTokenMutation.isPending ? (
<>
<LoadingSpinner className="mr-2 h-4 w-4" />
Updating...
</>
) : (
<>
<Save className="mr-2 h-4 w-4" />
Update Token
</>
)}
</Button>
</div>
</form>
</div>
</DialogContent>
</Dialog>
);
}