← back to Open Seo
src/client/features/domain/components/DomainSearchCard.tsx
147 lines
import type { FormEvent } from "react";
import { AlertCircle, Search } from "lucide-react";
import { getFieldError, getFormError } from "@/client/lib/forms";
import type { DomainOverviewControlsForm } from "@/client/features/domain/DomainOverviewPage";
import { toSortMode } from "@/client/features/domain/utils";
import type { DomainSortMode } from "@/client/features/domain/types";
import { LABS_LOCATION_OPTIONS } from "@/client/features/keywords/locations";
import { LocationSelect } from "@/client/components/LocationSelect";
type Props = {
controlsForm: DomainOverviewControlsForm;
isLoading: boolean;
onSubmit: (event: FormEvent) => void;
onSortChange: (sort: DomainSortMode) => void;
onLocationChange: (locationCode: number) => void;
};
export function DomainSearchCard({
controlsForm,
isLoading,
onSubmit,
onSortChange,
onLocationChange,
}: Props) {
return (
<div className="card bg-base-100 border border-base-300">
<div className="card-body gap-4">
<form
className="flex flex-col gap-3 lg:flex-row lg:items-center"
onSubmit={onSubmit}
>
<controlsForm.Field name="domain">
{(field) => {
const domainError = getFieldError(field.state.meta.errors);
return (
<label
className={`input input-bordered flex items-center gap-2 w-full lg:flex-1 lg:min-w-0 lg:max-w-md ${domainError ? "input-error" : ""}`}
>
<Search className="size-4 text-base-content/60" />
<input
className="grow min-w-0"
placeholder="Enter a domain"
value={field.state.value}
onChange={(event) => field.handleChange(event.target.value)}
aria-invalid={domainError ? true : undefined}
aria-describedby={
domainError ? "domain-input-error" : undefined
}
/>
</label>
);
}}
</controlsForm.Field>
<controlsForm.Field name="locationCode">
{(field) => (
<LocationSelect
value={field.state.value}
options={LABS_LOCATION_OPTIONS}
className="w-full lg:w-44 lg:shrink-0"
onChange={(code) => {
field.handleChange(code);
onLocationChange(code);
}}
/>
)}
</controlsForm.Field>
<controlsForm.Field name="sort">
{(field) => (
<select
className="select select-bordered shrink-0"
value={field.state.value}
onChange={(event) => {
const next = toSortMode(event.target.value) ?? "traffic";
field.handleChange(next);
onSortChange(next);
}}
>
<option value="rank">By Rank</option>
<option value="traffic">By Traffic</option>
<option value="volume">By Volume</option>
<option value="score">By Score</option>
<option value="cpc">By CPC</option>
</select>
)}
</controlsForm.Field>
<controlsForm.Subscribe selector={(state) => state.isSubmitting}>
{(isSubmitting) => (
<button
type="submit"
className="btn btn-primary shrink-0 px-6"
disabled={isLoading || isSubmitting}
>
{isLoading || isSubmitting ? "Loading..." : "Search"}
</button>
)}
</controlsForm.Subscribe>
</form>
<controlsForm.Field name="domain">
{(field) => {
const domainError = getFieldError(field.state.meta.errors);
return domainError ? (
<p id="domain-input-error" className="text-sm text-error">
{domainError}
</p>
) : null;
}}
</controlsForm.Field>
<controlsForm.Subscribe selector={(state) => state.errorMap.onSubmit}>
{(submitError) => {
const errorMessage = getFormError(submitError);
return errorMessage ? (
<div className="rounded-lg border border-error/30 bg-error/10 p-3 text-sm text-error flex items-start gap-2">
<AlertCircle className="size-4 shrink-0 mt-0.5" />
<span>{errorMessage}</span>
</div>
) : null;
}}
</controlsForm.Subscribe>
<div className="flex flex-wrap items-center gap-3">
<label className="label cursor-pointer gap-2 py-0">
<controlsForm.Field name="subdomains">
{(field) => (
<input
type="checkbox"
className="checkbox checkbox-sm"
checked={field.state.value}
onChange={(event) => field.handleChange(event.target.checked)}
/>
)}
</controlsForm.Field>
<span className="label-text">Include subdomains</span>
</label>
</div>
</div>
</div>
);
}