From b1844323314dd1f0832f1fcdb765a7f67ca01dbc Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Fri, 4 Apr 2025 18:29:22 +0200
Subject: [feature] Allow editing domain blocks/allows, fix comment import
 (#3967)

* start implementing editing of existing domain permissions

* [feature] Allow editing domain blocks/allows, fix comment import

* [bugfix] Use "comment" via /api/v1/instance

* fix the stuff
---
 .../views/moderation/domain-permissions/detail.tsx | 255 +++++++++++++--------
 1 file changed, 161 insertions(+), 94 deletions(-)

(limited to 'web/source/settings/views/moderation/domain-permissions/detail.tsx')

diff --git a/web/source/settings/views/moderation/domain-permissions/detail.tsx b/web/source/settings/views/moderation/domain-permissions/detail.tsx
index 0105d9615..e8ef487e3 100644
--- a/web/source/settings/views/moderation/domain-permissions/detail.tsx
+++ b/web/source/settings/views/moderation/domain-permissions/detail.tsx
@@ -32,8 +32,18 @@ import Loading from "../../../components/loading";
 import BackButton from "../../../components/back-button";
 import MutationButton from "../../../components/form/mutation-button";
 
-import { useDomainAllowsQuery, useDomainBlocksQuery } from "../../../lib/query/admin/domain-permissions/get";
-import { useAddDomainAllowMutation, useAddDomainBlockMutation, useRemoveDomainAllowMutation, useRemoveDomainBlockMutation } from "../../../lib/query/admin/domain-permissions/update";
+import { 
+	useDomainAllowsQuery,
+	useDomainBlocksQuery,
+} from "../../../lib/query/admin/domain-permissions/get";
+import {
+	useAddDomainAllowMutation,
+	useAddDomainBlockMutation,
+	useRemoveDomainAllowMutation,
+	useRemoveDomainBlockMutation,
+	useUpdateDomainAllowMutation,
+	useUpdateDomainBlockMutation,
+} from "../../../lib/query/admin/domain-permissions/update";
 import { DomainPerm } from "../../../lib/types/domain-permission";
 import { NoArg } from "../../../lib/types/query";
 import { Error } from "../../../components/error";
@@ -41,8 +51,10 @@ import { useBaseUrl } from "../../../lib/navigation/util";
 import { PermType } from "../../../lib/types/perm";
 import { useCapitalize } from "../../../lib/util";
 import { formDomainValidator } from "../../../lib/util/formvalidators";
+import UsernameLozenge from "../../../components/username-lozenge";
+import { FormSubmitEvent } from "../../../lib/form/types";
 
-export default function DomainPermDetail() {
+export default function DomainPermView() {
 	const baseUrl = useBaseUrl();
 	const search = useSearch();
 
@@ -101,33 +113,16 @@ export default function DomainPermDetail() {
 		? blocks[domain]
 		: allows[domain];
 	
-	// Render different into content depending on
-	// if we have a perm already for this domain.
-	let infoContent: React.JSX.Element;
-	if (existingPerm === undefined) {
-		infoContent = (
-			<span>
-				No stored {permType} yet, you can add one below:
-			</span>
-		);
-	} else {
-		infoContent = (
-			<div className="info">
-				<i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i>
-				<b>Editing existing domain {permTypeRaw} isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b>
-			</div>
-		);
-	}
+	const title = <span>Domain {permType} for {domain}</span>;
 
 	return (
-		<div>
-			<h1 className="text-cutoff">
-				<BackButton to={`~${baseUrl}/${permTypeRaw}`} />
-				{" "}
-				Domain {permType} for {domain}
-			</h1>
-			{infoContent}
-			<DomainPermForm
+		<div className="domain-permission-details">
+			<h1><BackButton to={`~${baseUrl}/${permTypeRaw}`} /> {title}</h1>
+			{ existingPerm
+				? <DomainPermDetails perm={existingPerm} permType={permType} />
+				: <span>No stored {permType} yet, you can add one below:</span>
+			}
+			<CreateOrUpdateDomainPerm
 				defaultDomain={domain}
 				perm={existingPerm}
 				permType={permType}
@@ -136,23 +131,75 @@ export default function DomainPermDetail() {
 	);
 }
 
-interface DomainPermFormProps {
+interface DomainPermDetailsProps {
+	perm: DomainPerm,
+	permType: PermType,
+}
+
+function DomainPermDetails({
+	perm,
+	permType
+}: DomainPermDetailsProps) {
+	const baseUrl = useBaseUrl();
+	const [ location ] = useLocation();
+	
+	const created = useMemo(() => {
+		if (perm.created_at) {
+			return new Date(perm.created_at).toDateString();
+		}
+		return "unknown";
+	}, [perm.created_at]);
+
+	return (
+		<dl className="info-list">
+			<div className="info-list-entry">
+				<dt>Created</dt>
+				<dd><time dateTime={perm.created_at}>{created}</time></dd>
+			</div>
+			<div className="info-list-entry">
+				<dt>Created By</dt>
+				<dd>
+					<UsernameLozenge
+						account={perm.created_by}
+						linkTo={`~/settings/moderation/accounts/${perm.created_by}`}
+						backLocation={`~${baseUrl}${location}`}
+					/>
+				</dd>
+			</div>
+			<div className="info-list-entry">
+				<dt>Domain</dt>
+				<dd>{perm.domain}</dd>
+			</div>
+			<div className="info-list-entry">
+				<dt>Permission type</dt>
+				<dd className={`permission-type ${permType}`}>
+					<i
+						aria-hidden={true}
+						className={`fa fa-${permType === "allow" ? "check" : "close"}`}
+					></i>
+					{permType}
+				</dd>
+			</div>
+			<div className="info-list-entry">
+				<dt>Subscription ID</dt>
+				<dd>{perm.subscription_id ?? "[none]"}</dd>
+			</div>
+		</dl>
+	);
+}
+
+interface CreateOrUpdateDomainPermProps {
 	defaultDomain: string;
 	perm?: DomainPerm;
 	permType: PermType;
 }
 
-function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps) {
+function CreateOrUpdateDomainPerm({
+	defaultDomain,
+	perm,
+	permType
+}: CreateOrUpdateDomainPermProps) {
 	const isExistingPerm = perm !== undefined;
-	const disabledForm = isExistingPerm
-		? {
-			disabled: true,
-			title: "Domain permissions currently cannot be edited."
-		}
-		: {
-			disabled: false,
-			title: "",
-		};
 
 	const form = {
 		domain: useTextInput("domain", {
@@ -161,8 +208,8 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
 			validator: formDomainValidator,
 		}),
 		obfuscate: useBoolInput("obfuscate", { source: perm }),
-		commentPrivate: useTextInput("private_comment", { source: perm }),
-		commentPublic: useTextInput("public_comment", { source: perm })
+		privateComment: useTextInput("private_comment", { source: perm }),
+		publicComment: useTextInput("public_comment", { source: perm })
 	};
 
 	// Check which perm type we're meant to be handling
@@ -171,112 +218,132 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
 	// react is like "weh" (mood), but we can decide
 	// which ones to use conditionally.
 	const [ addBlock, addBlockResult ] = useAddDomainBlockMutation();
+	const [ updateBlock, updateBlockResult ] = useUpdateDomainBlockMutation({ fixedCacheKey: perm?.id });
 	const [ removeBlock, removeBlockResult] = useRemoveDomainBlockMutation({ fixedCacheKey: perm?.id });
 	const [ addAllow, addAllowResult ] = useAddDomainAllowMutation();
+	const [ updateAllow, updateAllowResult ] = useUpdateDomainAllowMutation({ fixedCacheKey: perm?.id });
 	const [ removeAllow, removeAllowResult ] = useRemoveDomainAllowMutation({ fixedCacheKey: perm?.id });
 	
 	const [
-		addTrigger,
-		addResult,
+		createOrUpdateTrigger,
+		createOrUpdateResult,
 		removeTrigger,
 		removeResult,
 	] = useMemo(() => {
-		return permType == "block"
-			? [
-				addBlock,
-				addBlockResult,
-				removeBlock,
-				removeBlockResult,
-			]
-			: [
-				addAllow,
-				addAllowResult,
-				removeAllow,
-				removeAllowResult,
-			];
-	}, [permType,
-		addBlock, addBlockResult, removeBlock, removeBlockResult,
-		addAllow, addAllowResult, removeAllow, removeAllowResult,
+		switch (true) {
+			case (permType === "block" && !isExistingPerm):
+				return [ addBlock, addBlockResult, removeBlock, removeBlockResult ];
+			case (permType === "block"):
+				return [ updateBlock, updateBlockResult, removeBlock, removeBlockResult ];
+			case !isExistingPerm:
+				return [ addAllow, addAllowResult, removeAllow, removeAllowResult ];
+			default:
+				return [ updateAllow, updateAllowResult, removeAllow, removeAllowResult ];
+		}
+	}, [permType, isExistingPerm,
+		addBlock, addBlockResult, updateBlock, updateBlockResult, removeBlock, removeBlockResult,
+		addAllow, addAllowResult, updateAllow, updateAllowResult, removeAllow, removeAllowResult,
 	]);
 
-	// Use appropriate submission params for this permType.
-	const [submitForm, submitFormResult] = useFormSubmit(form, [addTrigger, addResult], { changedOnly: false });
+	// Use appropriate submission params for this
+	// permType, and whether we're creating or updating.
+	const [submit, submitResult] = useFormSubmit(
+		form,
+		[ createOrUpdateTrigger, createOrUpdateResult ],
+		{
+			changedOnly: isExistingPerm,
+			// If we're updating an existing perm,
+			// insert the perm ID into the mutation
+			// data before submitting. Otherwise just
+			// return the mutationData unmodified.
+			customizeMutationArgs: (mutationData) => {
+				if (isExistingPerm) {
+					return {
+						id: perm?.id,
+						...mutationData,
+					};
+				} else {
+					return mutationData;
+				}
+			},
+		},
+	);
 
 	// Uppercase first letter of given permType.
 	const permTypeUpper = useCapitalize(permType);
 
 	const [location, setLocation] = useLocation();
-
-	function verifyUrlThenSubmit(e) {
+	function onSubmit(e: FormSubmitEvent) {
 		// Adding a new domain permissions happens on a url like
 		// "/settings/admin/domain-permissions/:permType/domain.com",
 		// but if domain input changes, that doesn't match anymore
 		// and causes issues later on so, before submitting the form,
 		// silently change url, and THEN submit.
-		let correctUrl = `/${permType}s/${form.domain.value}`;
-		if (location != correctUrl) {
-			setLocation(correctUrl);
+		if (!isExistingPerm) {
+			let correctUrl = `/${permType}s/${form.domain.value}`;
+			if (location != correctUrl) {
+				setLocation(correctUrl);
+			}
 		}
-		return submitForm(e);
+		return submit(e);
 	}
 
 	return (
-		<form onSubmit={verifyUrlThenSubmit}>
-			<TextInput
-				field={form.domain}
-				label="Domain"
-				placeholder="example.com"
-				autoCapitalize="none"
-				spellCheck="false"
-				{...disabledForm}
-			/>
+		<form onSubmit={onSubmit}>
+			{ !isExistingPerm && 
+				<TextInput
+					field={form.domain}
+					label="Domain"
+					placeholder="example.com"
+					autoCapitalize="none"
+					spellCheck="false"
+				/>
+			}
 
 			<Checkbox
 				field={form.obfuscate}
 				label="Obfuscate domain in public lists"
-				{...disabledForm}
 			/>
 
 			<TextArea
-				field={form.commentPrivate}
+				field={form.privateComment}
 				label="Private comment"
 				autoCapitalize="sentences"
 				rows={3}
-				{...disabledForm}
 			/>
 
 			<TextArea
-				field={form.commentPublic}
+				field={form.publicComment}
 				label="Public comment"
 				autoCapitalize="sentences"
 				rows={3}
-				{...disabledForm}
 			/>
 
 			<div className="action-buttons row">
 				<MutationButton
-					label={permTypeUpper}
-					result={submitFormResult}
-					showError={false}
-					{...disabledForm}
+					label={isExistingPerm ? "Update " + permType.toString() : permTypeUpper}
+					result={submitResult}
+					disabled={
+						isExistingPerm &&
+						!form.obfuscate.hasChanged() &&
+						!form.privateComment.hasChanged() &&
+						!form.publicComment.hasChanged()
+					}
 				/>
 
-				{
-					isExistingPerm &&
-					<MutationButton
+				{ isExistingPerm &&
+					<button
 						type="button"
 						onClick={() => removeTrigger(perm.id?? "")}
-						label="Remove"
-						result={removeResult}
 						className="button danger"
-						showError={false}
-						disabled={!isExistingPerm}
-					/>
+					>
+						Remove {permType.toString()}
+					</button>
 				}
 			</div>
 
 			<>
-				{addResult.error && <Error error={addResult.error} />}
+				{createOrUpdateResult.error && <Error error={createOrUpdateResult.error} />}
 				{removeResult.error && <Error error={removeResult.error} />}
 			</>
 
-- 
cgit v1.2.3

