Extract inline forms from CategorySection into separate components
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Input, Td, Tr } from "@probo/ui";
|
||||
import { useState } from "react";
|
||||
|
||||
import type { CookieEntry } from "./CategorySection";
|
||||
|
||||
interface AddCookieRowProps {
|
||||
isUpdating: boolean;
|
||||
onSave: (cookie: CookieEntry) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function AddCookieRow({
|
||||
isUpdating,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: AddCookieRowProps) {
|
||||
const { __ } = useTranslate();
|
||||
const [form, setForm] = useState<CookieEntry>({
|
||||
name: "",
|
||||
duration: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={e => setForm({ ...form, name: e.target.value })}
|
||||
placeholder={__("Cookie name")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.duration}
|
||||
onChange={e => setForm({ ...form, duration: e.target.value })}
|
||||
placeholder={__("e.g. 1 year")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.description}
|
||||
onChange={e => setForm({ ...form, description: e.target.value })}
|
||||
placeholder={__("Description")}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => onSave(form)}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{__("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
@@ -24,10 +24,8 @@ import {
|
||||
IconPencil,
|
||||
IconPlusSmall,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Tbody,
|
||||
Td,
|
||||
Textarea,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
@@ -41,6 +39,16 @@ import type { CategorySectionFragment$key } from "#/__generated__/core/CategoryS
|
||||
import type { CategorySectionMoveCookieMutation } from "#/__generated__/core/CategorySectionMoveCookieMutation.graphql";
|
||||
import type { CategorySectionUpdateMutation } from "#/__generated__/core/CategorySectionUpdateMutation.graphql";
|
||||
|
||||
import { AddCookieRow } from "./AddCookieRow";
|
||||
import { EditCategoryForm } from "./EditCategoryForm";
|
||||
import { EditCookieRow } from "./EditCookieRow";
|
||||
|
||||
export interface CookieEntry {
|
||||
name: string;
|
||||
duration: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const categorySectionFragment = graphql`
|
||||
fragment CategorySectionFragment on CookieCategory {
|
||||
id
|
||||
@@ -134,12 +142,6 @@ const moveCookieMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
interface CookieEntry {
|
||||
name: string;
|
||||
duration: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface CategorySectionProps {
|
||||
categoryKey: CategorySectionFragment$key;
|
||||
onDelete?: () => void;
|
||||
@@ -156,18 +158,8 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
= useMutation<CategorySectionMoveCookieMutation>(moveCookieMutation);
|
||||
|
||||
const [isEditingCategory, setIsEditingCategory] = useState(false);
|
||||
const [editName, setEditName] = useState(category.name);
|
||||
const [editDescription, setEditDescription] = useState(category.description);
|
||||
|
||||
const [editingCookieIndex, setEditingCookieIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [editingCookieIndex, setEditingCookieIndex] = useState<number | null>(null);
|
||||
const [isAddingCookie, setIsAddingCookie] = useState(false);
|
||||
const [cookieForm, setCookieForm] = useState<CookieEntry>({
|
||||
name: "",
|
||||
duration: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const doUpdate = (
|
||||
input: Record<string, unknown>,
|
||||
@@ -209,48 +201,24 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveCategory = () => {
|
||||
doUpdate({ name: editName, description: editDescription }, () => {
|
||||
const handleSaveCategory = (name: string, description: string) => {
|
||||
doUpdate({ name, description }, () => {
|
||||
setIsEditingCategory(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelCategoryEdit = () => {
|
||||
setEditName(category.name);
|
||||
setEditDescription(category.description);
|
||||
setIsEditingCategory(false);
|
||||
};
|
||||
|
||||
const handleStartEditCookie = (index: number) => {
|
||||
const c = category.cookies[index];
|
||||
setCookieForm({
|
||||
name: c.name,
|
||||
duration: c.duration,
|
||||
description: c.description,
|
||||
});
|
||||
setEditingCookieIndex(index);
|
||||
setIsAddingCookie(false);
|
||||
};
|
||||
|
||||
const handleSaveEditCookie = () => {
|
||||
if (editingCookieIndex === null) return;
|
||||
if (!cookieForm.name.trim()) return;
|
||||
const handleSaveEditCookie = (index: number, cookie: CookieEntry) => {
|
||||
if (!cookie.name.trim()) return;
|
||||
const newCookies = category.cookies.map((c, i) =>
|
||||
i === editingCookieIndex
|
||||
? { ...cookieForm }
|
||||
i === index
|
||||
? { ...cookie }
|
||||
: { name: c.name, duration: c.duration, description: c.description },
|
||||
);
|
||||
doUpdate({ cookies: newCookies }, () => {
|
||||
setEditingCookieIndex(null);
|
||||
setCookieForm({ name: "", duration: "", description: "" });
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelEditCookie = () => {
|
||||
setEditingCookieIndex(null);
|
||||
setCookieForm({ name: "", duration: "", description: "" });
|
||||
};
|
||||
|
||||
const handleDeleteCookie = (index: number) => {
|
||||
const newCookies = category.cookies
|
||||
.filter((_, i) => i !== index)
|
||||
@@ -262,33 +230,21 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
doUpdate({ cookies: newCookies });
|
||||
};
|
||||
|
||||
const handleStartAddCookie = () => {
|
||||
setCookieForm({ name: "", duration: "", description: "" });
|
||||
setIsAddingCookie(true);
|
||||
setEditingCookieIndex(null);
|
||||
};
|
||||
|
||||
const handleSaveNewCookie = () => {
|
||||
if (!cookieForm.name.trim()) return;
|
||||
const handleSaveNewCookie = (cookie: CookieEntry) => {
|
||||
if (!cookie.name.trim()) return;
|
||||
const newCookies = [
|
||||
...category.cookies.map(c => ({
|
||||
name: c.name,
|
||||
duration: c.duration,
|
||||
description: c.description,
|
||||
})),
|
||||
{ ...cookieForm },
|
||||
{ ...cookie },
|
||||
];
|
||||
doUpdate({ cookies: newCookies }, () => {
|
||||
setIsAddingCookie(false);
|
||||
setCookieForm({ name: "", duration: "", description: "" });
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelAddCookie = () => {
|
||||
setIsAddingCookie(false);
|
||||
setCookieForm({ name: "", duration: "", description: "" });
|
||||
};
|
||||
|
||||
const allCategories = category.cookieBanner.categories.edges.map(e => e.node) ?? [];
|
||||
const siblingCategories = allCategories.filter(c => c.id !== category.id);
|
||||
|
||||
@@ -336,33 +292,13 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
<div className="p-4">
|
||||
{isEditingCategory
|
||||
? (
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
value={editName}
|
||||
onChange={e => setEditName(e.target.value)}
|
||||
placeholder={__("Category name")}
|
||||
/>
|
||||
<Textarea
|
||||
value={editDescription}
|
||||
onChange={e => setEditDescription(e.target.value)}
|
||||
placeholder={__("Category description")}
|
||||
rows={2}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={handleSaveCategory}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{isUpdating ? __("Saving...") : __("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleCancelCategoryEdit}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<EditCategoryForm
|
||||
name={category.name}
|
||||
description={category.description}
|
||||
isUpdating={isUpdating}
|
||||
onSave={handleSaveCategory}
|
||||
onCancel={() => setIsEditingCategory(false)}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -420,54 +356,17 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
{category.cookies.map((cookie, index) =>
|
||||
editingCookieIndex === index
|
||||
? (
|
||||
<Tr key={index}>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.name}
|
||||
onChange={e =>
|
||||
setCookieForm({ ...cookieForm, name: e.target.value })}
|
||||
placeholder={__("Cookie name")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.duration}
|
||||
onChange={e =>
|
||||
setCookieForm({
|
||||
...cookieForm,
|
||||
duration: e.target.value,
|
||||
})}
|
||||
placeholder={__("e.g. 1 year")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.description}
|
||||
onChange={e =>
|
||||
setCookieForm({
|
||||
...cookieForm,
|
||||
description: e.target.value,
|
||||
})}
|
||||
placeholder={__("Description")}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
onClick={handleSaveEditCookie}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{__("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleCancelEditCookie}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
<EditCookieRow
|
||||
key={index}
|
||||
cookie={{
|
||||
name: cookie.name,
|
||||
duration: cookie.duration,
|
||||
description: cookie.description,
|
||||
}}
|
||||
isUpdating={isUpdating}
|
||||
onSave={updated => handleSaveEditCookie(index, updated)}
|
||||
onCancel={() => setEditingCookieIndex(null)}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Tr key={index}>
|
||||
@@ -484,7 +383,10 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleStartEditCookie(index)}
|
||||
onClick={() => {
|
||||
setEditingCookieIndex(index);
|
||||
setIsAddingCookie(false);
|
||||
}}
|
||||
className="p-1 rounded cursor-pointer"
|
||||
>
|
||||
<IconPencil size={14} />
|
||||
@@ -524,51 +426,11 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
),
|
||||
)}
|
||||
{isAddingCookie && (
|
||||
<Tr>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.name}
|
||||
onChange={e =>
|
||||
setCookieForm({ ...cookieForm, name: e.target.value })}
|
||||
placeholder={__("Cookie name")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.duration}
|
||||
onChange={e =>
|
||||
setCookieForm({ ...cookieForm, duration: e.target.value })}
|
||||
placeholder={__("e.g. 1 year")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={cookieForm.description}
|
||||
onChange={e =>
|
||||
setCookieForm({
|
||||
...cookieForm,
|
||||
description: e.target.value,
|
||||
})}
|
||||
placeholder={__("Description")}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={handleSaveNewCookie}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{__("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleCancelAddCookie}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
<AddCookieRow
|
||||
isUpdating={isUpdating}
|
||||
onSave={handleSaveNewCookie}
|
||||
onCancel={() => setIsAddingCookie(false)}
|
||||
/>
|
||||
)}
|
||||
</Tbody>
|
||||
</table>
|
||||
@@ -577,7 +439,10 @@ export function CategorySection({ categoryKey, onDelete }: CategorySectionProps)
|
||||
<div className="p-3 border-t border-border-low">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleStartAddCookie}
|
||||
onClick={() => {
|
||||
setIsAddingCookie(true);
|
||||
setEditingCookieIndex(null);
|
||||
}}
|
||||
>
|
||||
<IconPlusSmall size={14} />
|
||||
{__("Add Cookie")}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Input, Textarea } from "@probo/ui";
|
||||
import { useState } from "react";
|
||||
|
||||
interface EditCategoryFormProps {
|
||||
name: string;
|
||||
description: string;
|
||||
isUpdating: boolean;
|
||||
onSave: (name: string, description: string) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function EditCategoryForm({
|
||||
name,
|
||||
description,
|
||||
isUpdating,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: EditCategoryFormProps) {
|
||||
const { __ } = useTranslate();
|
||||
const [editName, setEditName] = useState(name);
|
||||
const [editDescription, setEditDescription] = useState(description);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
value={editName}
|
||||
onChange={e => setEditName(e.target.value)}
|
||||
placeholder={__("Category name")}
|
||||
/>
|
||||
<Textarea
|
||||
value={editDescription}
|
||||
onChange={e => setEditDescription(e.target.value)}
|
||||
placeholder={__("Category description")}
|
||||
rows={2}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => onSave(editName, editDescription)}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{isUpdating ? __("Saving...") : __("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Input, Td, Tr } from "@probo/ui";
|
||||
import { useState } from "react";
|
||||
|
||||
import type { CookieEntry } from "./CategorySection";
|
||||
|
||||
interface EditCookieRowProps {
|
||||
cookie: CookieEntry;
|
||||
isUpdating: boolean;
|
||||
onSave: (cookie: CookieEntry) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function EditCookieRow({
|
||||
cookie,
|
||||
isUpdating,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: EditCookieRowProps) {
|
||||
const { __ } = useTranslate();
|
||||
const [form, setForm] = useState<CookieEntry>(cookie);
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={e => setForm({ ...form, name: e.target.value })}
|
||||
placeholder={__("Cookie name")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.duration}
|
||||
onChange={e => setForm({ ...form, duration: e.target.value })}
|
||||
placeholder={__("e.g. 1 year")}
|
||||
/>
|
||||
</Td>
|
||||
<Td className="pr-3">
|
||||
<Input
|
||||
value={form.description}
|
||||
onChange={e => setForm({ ...form, description: e.target.value })}
|
||||
placeholder={__("Description")}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
onClick={() => onSave(form)}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{__("Save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{__("Cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user