Create campaigns
This commit is contained in:
80
src/components/CreateCampaignButton.tsx
Normal file
80
src/components/CreateCampaignButton.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useState } from "react";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { useAuth } from "@/context/auth/AuthContext";
|
||||
import type { Campaign } from "@/lib/types";
|
||||
|
||||
/**
|
||||
* Button and form for creating a new campaign. Handles UI state and creation logic.
|
||||
*/
|
||||
export function CreateCampaignButton({ onCreated }: { onCreated?: (campaign: Campaign) => void }) {
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { user } = useAuth();
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!name.trim()) {
|
||||
setError("Campaign name is required.");
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const record = await pb.collection("campaigns").create({
|
||||
name,
|
||||
owner: user.id,
|
||||
});
|
||||
setName("");
|
||||
setCreating(false);
|
||||
if (onCreated) onCreated({ id: record.id, name: record.name });
|
||||
} catch (e: any) {
|
||||
setError(e?.message || "Failed to create campaign.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!creating) {
|
||||
return (
|
||||
<button
|
||||
className="px-4 py-2 rounded bg-violet-600 hover:bg-violet-700 text-white font-semibold transition-colors"
|
||||
onClick={() => setCreating(true)}
|
||||
>
|
||||
Create Campaign
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
className="px-3 py-2 rounded bg-slate-800 text-slate-100 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500"
|
||||
placeholder="Campaign name"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
disabled={loading}
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
className="px-4 py-2 rounded bg-emerald-600 hover:bg-emerald-700 text-white font-semibold transition-colors disabled:opacity-60"
|
||||
onClick={handleCreate}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Creating…" : "Create"}
|
||||
</button>
|
||||
<button
|
||||
className="px-2 py-2 rounded text-slate-400 hover:text-red-400"
|
||||
onClick={() => { setCreating(false); setName(""); setError(null); }}
|
||||
disabled={loading}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
{error && <span className="ml-2 text-red-400 text-sm">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
export type Id<T extends string> = string & { __type: T };
|
||||
|
||||
export type UserId = Id<"User">;
|
||||
export type CampaignId = Id<"Campaign">;
|
||||
export type DocumentId = Id<"Document">;
|
||||
|
||||
export type Campaign = {
|
||||
id: string;
|
||||
id: CampaignId;
|
||||
name: string;
|
||||
owner: UserId;
|
||||
};
|
||||
|
||||
export type Document = {
|
||||
id: string;
|
||||
id: DocumentId;
|
||||
campaign: Campaign;
|
||||
data: {};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { pb } from "@/lib/pocketbase";
|
||||
import type { Campaign } from "@/lib/types";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { CreateCampaignButton } from "@/components/CreateCampaignButton";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/_authenticated/campaigns/")({
|
||||
loader: async () => {
|
||||
@@ -15,6 +17,11 @@ export const Route = createFileRoute("/_authenticated/campaigns/")({
|
||||
|
||||
function RouteComponent() {
|
||||
const { campaigns } = Route.useLoaderData();
|
||||
const router = useRouter();
|
||||
|
||||
const handleCreated = async () => {
|
||||
await router.invalidate();
|
||||
};
|
||||
|
||||
if (!campaigns || campaigns.length === 0) return <div>No campaigns found.</div>;
|
||||
|
||||
@@ -34,6 +41,9 @@ function RouteComponent() {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-8">
|
||||
<CreateCampaignButton onCreated={handleCreated} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user