## 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>
98 lines
2.7 KiB
TypeScript
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>
|
|
);
|
|
}
|