multica/apps/web/features/modals/create-workspace.tsx
Naiyuan Qing 66b1defab7 refactor: migrate stores to features/, remove dead packages, add modals + workspace sync
## Store migration (packages → features)
- Delete `packages/store/` — stores moved into web app's feature modules
- Delete `packages/hooks/` — replaced by feature-level hooks
- `features/issues/store.ts` — useIssueStore (was packages/store/issue-store)
- `features/inbox/store.ts` — useInboxStore (was packages/store/inbox-store)
- `features/workspace/store.ts` — absorbs agent state (was packages/store/agent-store)
- All imports updated from `@multica/store` → `@/features/*/store`

## Global modal system
- `features/modals/store.ts` — useModalStore (zustand)
- `features/modals/registry.tsx` — ModalRegistry renders active modal
- Mounted in app/layout.tsx alongside Toaster
- Create Workspace dialog now works (was broken: DropdownMenu ate click)

## Workspace real-time sync
- useRealtimeSync subscribes to workspace:updated, member:removed
- Member removal → auto-switch to another workspace
- Workspace settings update → sidebar reflects name change
- Workspace switch → parallel fetch issues + inbox + agents

## Bug fixes
- theme-provider: guard event.key for IME composition (isComposing check)
- task.go: publish comment:created + inbox:new events on task complete/fail
- listeners.go: broadcast comment:created, workspace:updated, member events
- events.go: add EventCommentUpdated, EventCommentDeleted constants

## Cleanup
- Remove _features/ tracking files (dev-only, not for main)
- Remove server/server binary from worktree
- Update CLAUDE.md to reflect new architecture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:37:22 +08:00

98 lines
2.7 KiB
TypeScript

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { useWorkspaceStore } from "@/features/workspace";
export function CreateWorkspaceModal({ onClose }: { onClose: () => void }) {
const [name, setName] = useState("");
const [slug, setSlug] = useState("");
const [creating, setCreating] = useState(false);
const handleNameChange = (value: string) => {
setName(value);
setSlug(
value
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, ""),
);
};
const handleCreate = async () => {
if (!name.trim() || !slug.trim()) return;
setCreating(true);
try {
const { createWorkspace, switchWorkspace } =
useWorkspaceStore.getState();
const ws = await createWorkspace({
name: name.trim(),
slug: slug.trim(),
});
onClose();
await switchWorkspace(ws.id);
} catch {
toast.error("Failed to create workspace");
} finally {
setCreating(false);
}
};
return (
<Dialog open onOpenChange={(v) => { if (!v) onClose(); }}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create workspace</DialogTitle>
<DialogDescription>
Create a new workspace for your team.
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div>
<Label className="text-xs text-muted-foreground">Name</Label>
<Input
autoFocus
type="text"
value={name}
onChange={(e) => handleNameChange(e.target.value)}
placeholder="My Workspace"
className="mt-1"
/>
</div>
<div>
<Label className="text-xs text-muted-foreground">Slug</Label>
<Input
type="text"
value={slug}
onChange={(e) => setSlug(e.target.value)}
placeholder="my-workspace"
className="mt-1"
/>
</div>
</div>
<DialogFooter>
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
<Button
onClick={handleCreate}
disabled={creating || !name.trim() || !slug.trim()}
>
{creating ? "Creating..." : "Create"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}