← back to Open Seo
src/client/features/projects/CreateProjectModal.tsx
134 lines
import * as React from "react";
import { useNavigate } from "@tanstack/react-router";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Modal } from "@/client/components/Modal";
import { getStandardErrorMessage } from "@/client/lib/error-messages";
import { setLastProjectId } from "@/client/lib/active-project";
import {
DEFAULT_LOCATION_CODE,
getLanguageCode,
} from "@/client/features/keywords/locations";
import { ProjectMarketFields } from "@/client/features/projects/ProjectMarketFields";
import { createProject } from "@/serverFunctions/projects";
export function CreateProjectModal({ onClose }: { onClose: () => void }) {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [name, setName] = React.useState("");
const [domain, setDomain] = React.useState("");
const [market, setMarket] = React.useState({
locationCode: DEFAULT_LOCATION_CODE,
languageCode: getLanguageCode(DEFAULT_LOCATION_CODE),
});
const createMutation = useMutation({
mutationFn: () =>
createProject({
data: {
name: name.trim(),
domain: domain.trim() || undefined,
...market,
},
}),
onSuccess: async (created) => {
setLastProjectId(created.id);
await queryClient.invalidateQueries({ queryKey: ["projects"] });
onClose();
toast.success("Project created");
// Land on the new project's settings so they can connect Search Console
// and finish setting up the workspace.
void navigate({
to: "/p/$projectId/settings",
params: { projectId: created.id },
});
},
onError: (error) =>
toast.error(getStandardErrorMessage(error, "Failed to create project")),
});
const isPending = createMutation.isPending;
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (isPending) return;
if (!name.trim()) {
toast.error("Project name is required");
return;
}
createMutation.mutate();
};
return (
<Modal
maxWidth="max-w-md"
onClose={isPending ? undefined : onClose}
labelledBy="create-project-title"
>
<form onSubmit={handleSubmit} className="space-y-4">
<h2 id="create-project-title" className="text-lg font-semibold">
New project
</h2>
<label className="flex flex-col gap-1.5 text-sm">
<span className="font-medium">Name</span>
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Acme Inc."
maxLength={120}
autoFocus
className="input input-bordered w-full"
/>
</label>
<label className="flex flex-col gap-1.5 text-sm">
<span className="font-medium">
Domain <span className="text-base-content/50">(optional)</span>
</span>
<input
type="text"
value={domain}
onChange={(event) => setDomain(event.target.value)}
placeholder="example.com"
maxLength={255}
className="input input-bordered w-full"
/>
<span className="text-xs text-base-content/50">
You can connect Search Console and set up rank tracking after
creating the project.
</span>
</label>
<div className="flex flex-col gap-1.5">
<ProjectMarketFields value={market} onChange={setMarket} />
<span className="text-xs text-base-content/50">
Keyword, SERP, and domain data uses this country and language unless
a call asks for a different one. Change it later in project
settings.
</span>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
className="btn btn-ghost btn-sm"
onClick={onClose}
disabled={isPending}
>
Cancel
</button>
<button
type="submit"
className="btn btn-primary btn-sm"
disabled={isPending}
>
Create project
</button>
</div>
</form>
</Modal>
);
}