Files
stack/apps/web/src/components/team/TeamSettings.tsx
Jason Woltje e8a9a3087a
All checks were successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
fix(ci): fix pipeline #366 — web @mosaic/ui build, Dockerfile find bug, event handler types
Three root causes resolved:

1. .woodpecker/web.yml: build-shared step was missing @mosaic/ui build,
   causing 10 test suite failures + 20 typecheck errors (TS2307)

2. apps/orchestrator/Dockerfile: find -o without parentheses only deleted
   last pattern's matches, leaving spec files with test fixture secrets
   that triggered 5 Trivy false positives (3 CRITICAL, 2 HIGH)

3. 9 web files had untyped event handler parameters (e) causing 49 lint
   errors and 19 typecheck errors — added React.ChangeEvent<T> types

Verification: lint 0 errors, typecheck 0 errors, tests 73/73 suites pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:50:41 -06:00

147 lines
4.4 KiB
TypeScript

"use client";
import { useState } from "react";
import type { Team } from "@mosaic/shared";
import { Card, CardHeader, CardContent, CardFooter, Button, Input, Textarea } from "@mosaic/ui";
interface TeamSettingsProps {
team: Team;
onUpdate: (data: { name?: string; description?: string }) => Promise<void>;
onDelete: () => Promise<void>;
}
export function TeamSettings({ team, onUpdate, onDelete }: TeamSettingsProps): React.JSX.Element {
const [name, setName] = useState(team.name);
const [description, setDescription] = useState(team.description ?? "");
const [isEditing, setIsEditing] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const hasChanges = name !== team.name || description !== (team.description ?? "");
const handleSave = async (): Promise<void> => {
if (!hasChanges) return;
setIsSaving(true);
try {
const updates: { name?: string; description?: string } = {};
if (name !== team.name) {
updates.name = name;
}
if (description !== (team.description ?? "")) {
updates.description = description;
}
await onUpdate(updates);
setIsEditing(false);
} catch (error) {
console.error("Failed to update team:", error);
alert("Failed to update team. Please try again.");
} finally {
setIsSaving(false);
}
};
const handleCancel = (): void => {
setName(team.name);
setDescription(team.description ?? "");
setIsEditing(false);
};
const handleDelete = async (): Promise<void> => {
setIsDeleting(true);
try {
await onDelete();
} catch (error) {
console.error("Failed to delete team:", error);
alert("Failed to delete team. Please try again.");
setIsDeleting(false);
}
};
return (
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">Team Settings</h2>
</CardHeader>
<CardContent>
<div className="space-y-4">
<Input
label="Team Name"
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
setIsEditing(true);
}}
placeholder="Enter team name"
maxLength={100}
fullWidth
disabled={isSaving}
/>
<Textarea
label="Description"
value={description}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setDescription(e.target.value);
setIsEditing(true);
}}
placeholder="Enter team description (optional)"
maxLength={500}
fullWidth
disabled={isSaving}
/>
</div>
</CardContent>
<CardFooter>
<div className="flex items-center justify-between w-full">
<div className="flex gap-2">
{isEditing && (
<>
<Button
variant="primary"
onClick={handleSave}
disabled={!hasChanges || isSaving || !name.trim()}
>
{isSaving ? "Saving..." : "Save Changes"}
</Button>
<Button variant="ghost" onClick={handleCancel} disabled={isSaving}>
Cancel
</Button>
</>
)}
</div>
<div>
{!showDeleteConfirm ? (
<Button
variant="danger"
onClick={() => {
setShowDeleteConfirm(true);
}}
disabled={isSaving}
>
Delete Team
</Button>
) : (
<div className="flex gap-2">
<span className="text-sm text-gray-600 self-center">Are you sure?</span>
<Button variant="danger" onClick={handleDelete} disabled={isDeleting}>
{isDeleting ? "Deleting..." : "Confirm Delete"}
</Button>
<Button
variant="ghost"
onClick={() => {
setShowDeleteConfirm(false);
}}
disabled={isDeleting}
>
Cancel
</Button>
</div>
)}
</div>
</div>
</CardFooter>
</Card>
);
}