mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 08:18:19 +00:00
fix(ui): add loading spinners and toast feedback to create/update modals
- TeamMembersDialog: Loader2 spinner on add/remove, toast on success/error - TeamCreateDialog: Loader2 spinner on create button - TeamSettingsTab: Loader2 spinner on save button (was text-only) - ProviderFormDialog: Loader2 spinner on save/create button - Add i18n keys for member add/remove toast (en/vi/zh)
This commit is contained in:
1 parent
1b876231a1
commit
0a78902f9b
7 files changed
+50
-11
No files matched your search
@@ -42,6 +42,10 @@
|
||||
"searchAgents": "Search agents...",
|
||||
"adding": "Adding...",
|
||||
"add": "Add",
|
||||
"added": "Member added",
|
||||
"failedAdd": "Failed to add member",
|
||||
"removed": "Member removed",
|
||||
"failedRemove": "Failed to remove member",
|
||||
"removeMember": "Remove member",
|
||||
"columns": {
|
||||
"agent": "Agent",
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
"searchAgents": "Tìm kiếm agent...",
|
||||
"adding": "Đang thêm...",
|
||||
"add": "Thêm",
|
||||
"added": "Đã thêm thành viên",
|
||||
"failedAdd": "Thêm thành viên thất bại",
|
||||
"removed": "Đã xóa thành viên",
|
||||
"failedRemove": "Xóa thành viên thất bại",
|
||||
"removeMember": "Xóa thành viên",
|
||||
"columns": {
|
||||
"agent": "Agent",
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
"searchAgents": "搜索Agent...",
|
||||
"adding": "添加中...",
|
||||
"add": "添加",
|
||||
"added": "成员已添加",
|
||||
"failedAdd": "添加成员失败",
|
||||
"removed": "成员已移除",
|
||||
"failedRemove": "移除成员失败",
|
||||
"removeMember": "移除成员",
|
||||
"columns": {
|
||||
"agent": "Agent",
|
||||
|
||||
@@ -26,6 +26,7 @@ import { PROVIDER_TYPES } from "@/constants/providers";
|
||||
import { OAuthSection } from "./provider-oauth-section";
|
||||
import { CLISection } from "./provider-cli-section";
|
||||
import { ACPSection } from "./provider-acp-section";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
interface ProviderFormDialogProps {
|
||||
open: boolean;
|
||||
@@ -293,7 +294,9 @@ export function ProviderFormDialog({ open, onOpenChange, provider, onSubmit, exi
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!name.trim() || !isValidSlug(name) || !providerType || loading}
|
||||
className="gap-1"
|
||||
>
|
||||
{loading && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
||||
{loading
|
||||
? (isEdit ? t("form.saving") : t("form.creating"))
|
||||
: isEdit ? t("form.save") : t("form.create")}
|
||||
|
||||
@@ -5,9 +5,10 @@ import {
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { Bot, UserPlus, X } from "lucide-react";
|
||||
import { Bot, UserPlus, X, Loader2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAgents } from "@/pages/agents/hooks/use-agents";
|
||||
import { toast } from "@/stores/use-toast-store";
|
||||
import type { TeamMemberData } from "@/types/team";
|
||||
|
||||
interface TeamMembersDialogProps {
|
||||
@@ -67,6 +68,8 @@ export function TeamMembersDialog({
|
||||
[members],
|
||||
);
|
||||
|
||||
const [removing, setRemoving] = useState<string | null>(null);
|
||||
|
||||
const handleAdd = async () => {
|
||||
if (!selected) return;
|
||||
setAdding(true);
|
||||
@@ -74,8 +77,24 @@ export function TeamMembersDialog({
|
||||
await onAddMember(selected, "member");
|
||||
setSelected("");
|
||||
setShowAdd(false);
|
||||
} catch { /* handled upstream */ }
|
||||
finally { setAdding(false); }
|
||||
toast.success(t("members.added"));
|
||||
} catch {
|
||||
toast.error(t("members.failedAdd"));
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async (agentId: string) => {
|
||||
setRemoving(agentId);
|
||||
try {
|
||||
await onRemoveMember(agentId);
|
||||
toast.success(t("members.removed"));
|
||||
} catch {
|
||||
toast.error(t("members.failedRemove"));
|
||||
} finally {
|
||||
setRemoving(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -104,7 +123,8 @@ export function TeamMembersDialog({
|
||||
placeholder={t("members.searchAgents")}
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="h-9 shrink-0" disabled={!selected || adding} onClick={handleAdd}>
|
||||
<Button size="sm" className="h-9 shrink-0 gap-1" disabled={!selected || adding} onClick={handleAdd}>
|
||||
{adding && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
||||
{t("members.add")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -139,10 +159,11 @@ export function TeamMembersDialog({
|
||||
</div>
|
||||
{m.role !== "lead" && (
|
||||
<button
|
||||
className="mt-0.5 shrink-0 cursor-pointer text-muted-foreground opacity-0 transition-opacity hover:text-destructive group-hover:opacity-100"
|
||||
onClick={() => onRemoveMember(m.agent_id)}
|
||||
className="mt-0.5 shrink-0 cursor-pointer text-muted-foreground opacity-0 transition-opacity hover:text-destructive group-hover:opacity-100 disabled:opacity-50"
|
||||
onClick={() => handleRemove(m.agent_id)}
|
||||
disabled={removing === m.agent_id}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
{removing === m.agent_id ? <Loader2 className="h-4 w-4 animate-spin" /> : <X className="h-4 w-4" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { X, Info } from "lucide-react";
|
||||
import { X, Info, Loader2 } from "lucide-react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -199,7 +199,8 @@ export function TeamCreateDialog({ open, onOpenChange, onCreate }: TeamCreateDia
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={loading}>
|
||||
{t("create.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleCreate} disabled={!name.trim() || !leadOptions.some((o) => o.value === lead) || loading}>
|
||||
<Button onClick={handleCreate} disabled={!name.trim() || !leadOptions.some((o) => o.value === lead) || loading} className="gap-1">
|
||||
{loading && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
||||
{loading ? t("create.creating") : t("create.create")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { X, Save, Check, Bell, ShieldAlert, Clock, Info, FolderLock, FolderSync, Zap, Bot } from "lucide-react";
|
||||
import { X, Save, Check, Bell, ShieldAlert, Clock, Info, FolderLock, FolderSync, Zap, Bot, Loader2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CHANNEL_TYPES } from "@/constants/channels";
|
||||
import type { TeamData, TeamAccessSettings, EscalationMode, EscalationAction } from "@/types/team";
|
||||
@@ -449,7 +449,9 @@ export function TeamSettingsTab({ teamId, team, onSaved }: TeamSettingsTabProps)
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={handleSave} disabled={saving} className="gap-2">
|
||||
{saving ? (
|
||||
t("settings.saving")
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> {t("settings.saving")}
|
||||
</>
|
||||
) : saved ? (
|
||||
<>
|
||||
<Check className="h-4 w-4" /> {t("settings.saved")}
|
||||
|
||||
Reference in new issue
Block a user