feat(board): add cards as owner, request features as client
This commit is contained in:
parent
07656a5bc7
commit
043d8ddc8c
2 changed files with 142 additions and 0 deletions
132
src/components/board/add-card.tsx
Normal file
132
src/components/board/add-card.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Plus } from "lucide-react";
|
||||
|
||||
import { createCard } from "@/app/actions/cards";
|
||||
import type { Role } from "@/lib/pb/types";
|
||||
|
||||
export function AddCard({
|
||||
role,
|
||||
projectId,
|
||||
columnId,
|
||||
clientSlug,
|
||||
projectSlug,
|
||||
}: {
|
||||
role: Role;
|
||||
projectId: string;
|
||||
columnId: string;
|
||||
clientSlug: string;
|
||||
projectSlug: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState("");
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Takes the minimal shape rather than React.FormEvent, because it is called
|
||||
// both from onSubmit and from the Enter handler in onKeyDown, and those
|
||||
// carry different event types.
|
||||
async function submit(event: { preventDefault: () => void }) {
|
||||
event.preventDefault();
|
||||
const value = title.trim();
|
||||
if (!value) return;
|
||||
|
||||
setError(null);
|
||||
|
||||
if (role === "owner") {
|
||||
setTitle("");
|
||||
setOpen(false);
|
||||
startTransition(() =>
|
||||
createCard({
|
||||
projectId,
|
||||
columnId,
|
||||
title: value,
|
||||
clientSlug,
|
||||
projectSlug,
|
||||
}).then(() => undefined)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// A client manager's card is a request: the route decides the column,
|
||||
// the priority and the accepted flag, whatever is sent here.
|
||||
const response = await fetch("/api/client/cards", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ project: projectId, title: value }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
setError("Could not add that");
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle("");
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(true)}
|
||||
className="flex items-center gap-1.5 rounded-[var(--radius-tile)] px-2 py-2 text-xs font-medium text-text-muted transition-colors hover:bg-surface-raised hover:text-text-secondary"
|
||||
>
|
||||
<Plus size={13} strokeWidth={2} />
|
||||
{role === "owner" ? "Add a card" : "Request a feature"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={submit}
|
||||
className="rounded-[var(--radius-tile)] border border-border-subtle bg-panel p-2"
|
||||
>
|
||||
<textarea
|
||||
autoFocus
|
||||
value={title}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Escape") setOpen(false);
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
submit(event);
|
||||
}
|
||||
}}
|
||||
rows={2}
|
||||
placeholder={
|
||||
role === "owner" ? "What needs doing?" : "What would you like added?"
|
||||
}
|
||||
className="w-full resize-none bg-transparent text-[13px] leading-[1.45] text-text-primary outline-none placeholder:text-text-muted"
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<p role="alert" className="px-0.5 pb-1 text-[11px] text-accent">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={pending || !title.trim()}
|
||||
className="rounded-md bg-brand px-2.5 py-1 text-xs font-medium text-brand-fg disabled:opacity-50"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="text-xs text-text-muted hover:text-text-secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
import { AddCard } from "@/components/board/add-card";
|
||||
import { BoardColumn } from "@/components/board/board-column";
|
||||
import { CardTile } from "@/components/board/card-tile";
|
||||
import { ClientCardControls } from "@/components/board/client-card-controls";
|
||||
|
|
@ -233,6 +234,15 @@ export function Board({
|
|||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
{(role === "owner" || column.is_inbox) && (
|
||||
<AddCard
|
||||
role={role}
|
||||
projectId={projectId}
|
||||
columnId={column.id}
|
||||
clientSlug={clientSlug}
|
||||
projectSlug={projectSlug}
|
||||
/>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
Loading…
Reference in a new issue