mirror of
				https://github.com/superseriousbusiness/gotosocial
				synced 2025-06-05 21:59:39 +02:00 
			
		
		
		
	[feature] Move + alias account via settings panel (#2519)
* [feature] Move + alias account via settings panel * lint * type a bit more diligently
This commit is contained in:
		@@ -30,6 +30,10 @@ const Loading = require("./components/loading");
 | 
			
		||||
const UserLogoutCard = require("./components/user-logout-card");
 | 
			
		||||
const { RoleContext } = require("./lib/navigation/util");
 | 
			
		||||
 | 
			
		||||
const UserProfile = require("./user/profile").default;
 | 
			
		||||
const UserSettings = require("./user/settings").default;
 | 
			
		||||
const UserMigration = require("./user/migration").default;
 | 
			
		||||
 | 
			
		||||
const DomainPerms = require("./admin/domain-permissions").default;
 | 
			
		||||
const DomainPermsImportExport = require("./admin/domain-permissions/import-export").default;
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +43,9 @@ require("./style.css");
 | 
			
		||||
 | 
			
		||||
const { Sidebar, ViewRouter } = createNavigation("/settings", [
 | 
			
		||||
	Menu("User", [
 | 
			
		||||
		Item("Profile", { icon: "fa-user" }, require("./user/profile")),
 | 
			
		||||
		Item("Settings", { icon: "fa-cogs" }, require("./user/settings")),
 | 
			
		||||
		Item("Profile", { icon: "fa-user" }, UserProfile),
 | 
			
		||||
		Item("Settings", { icon: "fa-cogs" }, UserSettings),
 | 
			
		||||
		Item("Migration", { icon: "fa-exchange" }, UserMigration),
 | 
			
		||||
	]),
 | 
			
		||||
	Menu("Moderation", {
 | 
			
		||||
		url: "admin",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								web/source/settings/lib/form/array.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								web/source/settings/lib/form/array.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
/*
 | 
			
		||||
	GoToSocial
 | 
			
		||||
	Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
			
		||||
	SPDX-License-Identifier: AGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
	This program is free software: you can redistribute it and/or modify
 | 
			
		||||
	it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
	the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
	(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
	This program is distributed in the hope that it will be useful,
 | 
			
		||||
	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
	GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
	You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { useRef, useMemo } from "react";
 | 
			
		||||
 | 
			
		||||
import type {
 | 
			
		||||
	CreateHookNames,
 | 
			
		||||
	HookOpts,
 | 
			
		||||
	ArrayInputHook,
 | 
			
		||||
	HookedForm,
 | 
			
		||||
} from "./types";
 | 
			
		||||
import getFormMutations from "./get-form-mutations";
 | 
			
		||||
 | 
			
		||||
function parseFields(entries: HookedForm[], length: number): HookedForm[] {
 | 
			
		||||
	const fields: HookedForm[] = [];
 | 
			
		||||
 | 
			
		||||
	for (let i = 0; i < length; i++) {
 | 
			
		||||
		if (entries[i] != undefined) {
 | 
			
		||||
			fields[i] = Object.assign({}, entries[i]);
 | 
			
		||||
		} else {
 | 
			
		||||
			fields[i] = {};
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fields;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function useArrayInput(
 | 
			
		||||
	{ name }: CreateHookNames,
 | 
			
		||||
	{
 | 
			
		||||
		initialValue,
 | 
			
		||||
		length = 0,
 | 
			
		||||
	}: HookOpts,
 | 
			
		||||
): ArrayInputHook {
 | 
			
		||||
	const _default: HookedForm[] = Array(length);
 | 
			
		||||
	const fields = useRef<HookedForm[]>(_default);
 | 
			
		||||
 | 
			
		||||
	const value = useMemo(
 | 
			
		||||
		() => parseFields(initialValue, length),
 | 
			
		||||
		[initialValue, length],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	function hasUpdate() {
 | 
			
		||||
		return Object.values(fields.current).some((fieldSet) => {
 | 
			
		||||
			const { updatedFields } = getFormMutations(fieldSet, { changedOnly: true });
 | 
			
		||||
			return updatedFields.length > 0;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		_default,
 | 
			
		||||
		name,
 | 
			
		||||
		Name: "",
 | 
			
		||||
		value,
 | 
			
		||||
		ctx: fields.current,
 | 
			
		||||
		maxLength: length,
 | 
			
		||||
		hasChanged: hasUpdate,
 | 
			
		||||
		selectedValues() {
 | 
			
		||||
			if (hasUpdate()) {
 | 
			
		||||
				return Object.values(fields.current)
 | 
			
		||||
					// Extract all form fields.
 | 
			
		||||
					.flatMap((fieldSet) => {
 | 
			
		||||
						return getFormMutations(
 | 
			
		||||
							fieldSet,
 | 
			
		||||
							{ changedOnly: false },
 | 
			
		||||
						).updatedFields;
 | 
			
		||||
					})
 | 
			
		||||
					// Get just value from each
 | 
			
		||||
					// field, discarding name.
 | 
			
		||||
					.map((field) => field.value);
 | 
			
		||||
			} else {
 | 
			
		||||
				return [];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
@@ -42,7 +42,7 @@ function parseFields(entries: HookedForm[], length: number): HookedForm[] {
 | 
			
		||||
	return fields;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function useArrayInput(
 | 
			
		||||
export default function useFieldArrayInput(
 | 
			
		||||
	{ name }: CreateHookNames,
 | 
			
		||||
	{
 | 
			
		||||
		initialValue,
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,12 @@ import { FormInputHook, HookedForm } from "./types";
 | 
			
		||||
export default function getFormMutations(
 | 
			
		||||
	form: HookedForm,
 | 
			
		||||
	{ changedOnly }: { changedOnly: boolean },
 | 
			
		||||
) {
 | 
			
		||||
): {
 | 
			
		||||
	updatedFields: FormInputHook<any>[];
 | 
			
		||||
	mutationData: {
 | 
			
		||||
		[k: string]: any;
 | 
			
		||||
	};
 | 
			
		||||
} {
 | 
			
		||||
	const updatedFields: FormInputHook[] = [];
 | 
			
		||||
	const mutationData: Array<[string, any]> = [];
 | 
			
		||||
	
 | 
			
		||||
@@ -34,7 +39,7 @@ export default function getFormMutations(
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if ("selectedValues" in field) {
 | 
			
		||||
			// FieldArrayInputHook.
 | 
			
		||||
			// (Field)ArrayInputHook.
 | 
			
		||||
			const selected = field.selectedValues();
 | 
			
		||||
			if (!changedOnly || selected.length > 0) {
 | 
			
		||||
				updatedFields.push(field);
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import bool from "./bool";
 | 
			
		||||
import radio from "./radio";
 | 
			
		||||
import combobox from "./combo-box";
 | 
			
		||||
import checklist from "./check-list";
 | 
			
		||||
import array from "./array";
 | 
			
		||||
import fieldarray from "./field-array";
 | 
			
		||||
 | 
			
		||||
import type {
 | 
			
		||||
@@ -37,8 +38,9 @@ import type {
 | 
			
		||||
	FileFormInputHook,
 | 
			
		||||
	BoolFormInputHook,
 | 
			
		||||
	ComboboxFormInputHook,
 | 
			
		||||
	FieldArrayInputHook,
 | 
			
		||||
	ChecklistInputHook,
 | 
			
		||||
	FieldArrayInputHook,
 | 
			
		||||
	ArrayInputHook,
 | 
			
		||||
} from "./types";
 | 
			
		||||
 | 
			
		||||
function capitalizeFirst(str: string) {
 | 
			
		||||
@@ -110,5 +112,6 @@ export const useBoolInput = inputHook(bool) as (_name: string, _opts?: HookOpts<
 | 
			
		||||
export const useRadioInput = inputHook(radio) as (_name: string, _opts?: HookOpts<string>) => RadioFormInputHook;
 | 
			
		||||
export const useComboBoxInput = inputHook(combobox) as (_name: string, _opts?: HookOpts<string>) => ComboboxFormInputHook;
 | 
			
		||||
export const useCheckListInput = inputHook(checklist) as (_name: string, _opts?: HookOpts<boolean>) => ChecklistInputHook;
 | 
			
		||||
export const useArrayInput = inputHook(array) as (_name: string, _opts?: HookOpts<string[]>) => ArrayInputHook;
 | 
			
		||||
export const useFieldArrayInput = inputHook(fieldarray) as (_name: string, _opts?: HookOpts<string>) => FieldArrayInputHook;
 | 
			
		||||
export const useValue = value as <T>(_name: string, _initialValue: T) => FormInputHook<T>;
 | 
			
		||||
 
 | 
			
		||||
@@ -141,6 +141,10 @@ interface _withNew {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface _withSelectedValues {
 | 
			
		||||
	selectedValues: () => string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface _withSelectedFieldValues {
 | 
			
		||||
	selectedValues: () => {
 | 
			
		||||
		[_: string]: any;
 | 
			
		||||
	}[]
 | 
			
		||||
@@ -200,11 +204,16 @@ export interface ComboboxFormInputHook extends FormInputHook<string>,
 | 
			
		||||
	_withNew,
 | 
			
		||||
	_withReset {}
 | 
			
		||||
 | 
			
		||||
export interface FieldArrayInputHook extends FormInputHook<HookedForm[]>,
 | 
			
		||||
export interface ArrayInputHook extends FormInputHook<HookedForm[]>,
 | 
			
		||||
	_withSelectedValues,
 | 
			
		||||
	_withMaxLength,
 | 
			
		||||
	_withCtx {}
 | 
			
		||||
 | 
			
		||||
export interface FieldArrayInputHook extends FormInputHook<HookedForm[]>,
 | 
			
		||||
	_withSelectedFieldValues,
 | 
			
		||||
	_withMaxLength,
 | 
			
		||||
	_withCtx {}
 | 
			
		||||
 | 
			
		||||
export interface Checkable {
 | 
			
		||||
	key: string;
 | 
			
		||||
	checked?: boolean;
 | 
			
		||||
@@ -213,7 +222,7 @@ export interface Checkable {
 | 
			
		||||
export interface ChecklistInputHook<T = Checkable> extends FormInputHook<{[k: string]: T}>,
 | 
			
		||||
	_withReset,
 | 
			
		||||
	_withToggleAll,
 | 
			
		||||
	_withSelectedValues,
 | 
			
		||||
	_withSelectedFieldValues,
 | 
			
		||||
	_withSomeSelected,
 | 
			
		||||
	_withUpdateMultiple {
 | 
			
		||||
		// Uses its own funky onChange handler.
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,10 @@
 | 
			
		||||
 | 
			
		||||
import { replaceCacheOnMutation } from "../query-modifiers";
 | 
			
		||||
import { gtsApi } from "../gts-api";
 | 
			
		||||
import type {
 | 
			
		||||
	MoveAccountFormData,
 | 
			
		||||
	UpdateAliasesFormData
 | 
			
		||||
} from "../../types/migration";
 | 
			
		||||
 | 
			
		||||
const extended = gtsApi.injectEndpoints({
 | 
			
		||||
	endpoints: (build) => ({
 | 
			
		||||
@@ -38,6 +42,30 @@ const extended = gtsApi.injectEndpoints({
 | 
			
		||||
				url: `/api/v1/user/password_change`,
 | 
			
		||||
				body: data
 | 
			
		||||
			})
 | 
			
		||||
		}),
 | 
			
		||||
		aliasAccount: build.mutation<any, UpdateAliasesFormData>({
 | 
			
		||||
			async queryFn(formData, _api, _extraOpts, fetchWithBQ) {
 | 
			
		||||
				// Pull entries out from the hooked form.
 | 
			
		||||
				const entries: String[] = [];
 | 
			
		||||
				formData.also_known_as_uris.forEach(entry => {
 | 
			
		||||
					if (entry) {
 | 
			
		||||
						entries.push(entry);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				return fetchWithBQ({
 | 
			
		||||
					method: "POST",
 | 
			
		||||
					url: `/api/v1/accounts/alias`,
 | 
			
		||||
					body: { also_known_as_uris: entries },
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		moveAccount: build.mutation<any, MoveAccountFormData>({
 | 
			
		||||
			query: (data) => ({
 | 
			
		||||
				method: "POST",
 | 
			
		||||
				url: `/api/v1/accounts/move`,
 | 
			
		||||
				body: data
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
});
 | 
			
		||||
@@ -45,4 +73,6 @@ const extended = gtsApi.injectEndpoints({
 | 
			
		||||
export const {
 | 
			
		||||
	useUpdateCredentialsMutation,
 | 
			
		||||
	usePasswordChangeMutation,
 | 
			
		||||
	useAliasAccountMutation,
 | 
			
		||||
	useMoveAccountMutation,
 | 
			
		||||
} = extended;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								web/source/settings/lib/types/migration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/source/settings/lib/types/migration.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
	GoToSocial
 | 
			
		||||
	Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
			
		||||
	SPDX-License-Identifier: AGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
	This program is free software: you can redistribute it and/or modify
 | 
			
		||||
	it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
	the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
	(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
	This program is distributed in the hope that it will be useful,
 | 
			
		||||
	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
	GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
	You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
export interface UpdateAliasesFormData {
 | 
			
		||||
	also_known_as_uris: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MoveAccountFormData {
 | 
			
		||||
	moved_to_uri: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -51,7 +51,8 @@ ul li::before {
 | 
			
		||||
		border-radius: $br;
 | 
			
		||||
		max-width: 100%;
 | 
			
		||||
 | 
			
		||||
		& > div, & > form {
 | 
			
		||||
		& > div,
 | 
			
		||||
		& > form {
 | 
			
		||||
			border-left: 0.2rem solid $border-accent;
 | 
			
		||||
			padding-left: 0.4rem;
 | 
			
		||||
			display: flex;
 | 
			
		||||
@@ -59,13 +60,14 @@ ul li::before {
 | 
			
		||||
			gap: 0.5rem;
 | 
			
		||||
			margin: 1rem 0;
 | 
			
		||||
 | 
			
		||||
			h2 {
 | 
			
		||||
			h1, h2 {
 | 
			
		||||
				margin: 0;
 | 
			
		||||
				margin-top: 0.1rem;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:only-child {
 | 
			
		||||
				border-left: none;
 | 
			
		||||
				padding-left: none;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&:first-child {
 | 
			
		||||
@@ -76,7 +78,8 @@ ul li::before {
 | 
			
		||||
				margin-bottom: 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			&.without-border {
 | 
			
		||||
			&.without-border,
 | 
			
		||||
			.without-border {
 | 
			
		||||
				border-left: 0;
 | 
			
		||||
				padding-left: 0;
 | 
			
		||||
			}
 | 
			
		||||
@@ -410,6 +413,19 @@ section.with-sidebar > div, section.with-sidebar > form {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Normalize mock profile and make profile
 | 
			
		||||
	header preview pop a bit nicer.
 | 
			
		||||
*/
 | 
			
		||||
.profile {
 | 
			
		||||
	padding: 0;
 | 
			
		||||
 | 
			
		||||
	& > .profile-header {
 | 
			
		||||
		margin-bottom: 0;
 | 
			
		||||
		border: 0.1rem solid $gray1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-profile {
 | 
			
		||||
	.overview {
 | 
			
		||||
		display: grid;
 | 
			
		||||
@@ -418,14 +434,6 @@ section.with-sidebar > div, section.with-sidebar > form {
 | 
			
		||||
		grid-template-rows: 100%;
 | 
			
		||||
		gap: 1rem;
 | 
			
		||||
 | 
			
		||||
		.profile {
 | 
			
		||||
			padding: 0;
 | 
			
		||||
 | 
			
		||||
			.header {
 | 
			
		||||
				border: 0.1rem solid $gray1;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.files {
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			display: flex;
 | 
			
		||||
@@ -451,6 +459,36 @@ section.with-sidebar > div, section.with-sidebar > form {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.migration-details {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	gap: 1rem;
 | 
			
		||||
 | 
			
		||||
	background-color: $gray2;
 | 
			
		||||
	padding: 1rem;
 | 
			
		||||
	max-width: fit-content;
 | 
			
		||||
	border-radius: $br;
 | 
			
		||||
 | 
			
		||||
	& > div {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		flex-direction: column;
 | 
			
		||||
		gap: 0.25rem;
 | 
			
		||||
 | 
			
		||||
		& > dd {
 | 
			
		||||
			font-weight: bold;
 | 
			
		||||
			word-wrap: anywhere;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-migration-alias {
 | 
			
		||||
	.aliases {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		flex-direction: column;
 | 
			
		||||
		gap: 0.5rem;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										206
									
								
								web/source/settings/user/migration.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								web/source/settings/user/migration.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
			
		||||
/*
 | 
			
		||||
	GoToSocial
 | 
			
		||||
	Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
			
		||||
	SPDX-License-Identifier: AGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
	This program is free software: you can redistribute it and/or modify
 | 
			
		||||
	it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
	the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
	(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
	This program is distributed in the hope that it will be useful,
 | 
			
		||||
	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
	GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
	You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import FormWithData from "../lib/form/form-with-data";
 | 
			
		||||
 | 
			
		||||
import { useVerifyCredentialsQuery } from "../lib/query/oauth";
 | 
			
		||||
import { useArrayInput, useTextInput } from "../lib/form";
 | 
			
		||||
import { TextInput } from "../components/form/inputs";
 | 
			
		||||
import useFormSubmit from "../lib/form/submit";
 | 
			
		||||
import MutationButton from "../components/form/mutation-button";
 | 
			
		||||
import { useAliasAccountMutation, useMoveAccountMutation } from "../lib/query/user";
 | 
			
		||||
import { FormContext, useWithFormContext } from "../lib/form/context";
 | 
			
		||||
import { store } from "../redux/store";
 | 
			
		||||
 | 
			
		||||
export default function UserMigration() {
 | 
			
		||||
	return (
 | 
			
		||||
		<FormWithData
 | 
			
		||||
			dataQuery={useVerifyCredentialsQuery}
 | 
			
		||||
			DataForm={UserMigrationForm}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function UserMigrationForm({ data: profile }) {
 | 
			
		||||
	let urlStr = store.getState().oauth.instanceUrl ?? "";
 | 
			
		||||
	let url = new URL(urlStr);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<h2>Account Migration Settings</h2>
 | 
			
		||||
			<p>
 | 
			
		||||
				The following settings allow you to <strong>alias</strong> your account to another account
 | 
			
		||||
				elsewhere, and to <strong>move</strong> your followers and following lists to another account.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				Account <strong>aliasing</strong> is harmless and reversible; you can
 | 
			
		||||
				set and unset up to five account aliases as many times as you wish.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				The account <strong>move</strong> action, on the other hand, has serious and irreversible consequences.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				To move, you must set an alias from your account to the target account, using this settings panel.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				You must also set an alias from the target account back to your account, using
 | 
			
		||||
				the settings panel of the instance on which the target account resides.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				Provide the following details to the other instance: 
 | 
			
		||||
			</p>
 | 
			
		||||
			<dl className="migration-details">
 | 
			
		||||
				<div>
 | 
			
		||||
					<dt>Account handle/username:</dt>
 | 
			
		||||
					<dd>@{profile.acct}@{url.host}</dd>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div>
 | 
			
		||||
					<dt>Account URI:</dt>
 | 
			
		||||
					<dd>{urlStr}/users/{profile.username}</dd>
 | 
			
		||||
				</div>
 | 
			
		||||
			</dl>
 | 
			
		||||
			<p>
 | 
			
		||||
				For more information on account migration, please see <a href="https://docs.gotosocial.org/en/latest/user_guide/settings/#alias-account" target="_blank" className="docslink" rel="noreferrer">the documentation</a>.
 | 
			
		||||
			</p>
 | 
			
		||||
			<AliasForm data={profile} />
 | 
			
		||||
			<MoveForm data={profile} />
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AliasForm({ data: profile }) {
 | 
			
		||||
	const form = {
 | 
			
		||||
		alsoKnownAs: useArrayInput("also_known_as_uris", {
 | 
			
		||||
			source: profile,
 | 
			
		||||
			valueSelector: (p) => (
 | 
			
		||||
				p.source?.also_known_as_uris
 | 
			
		||||
					? p.source?.also_known_as_uris.map(entry => [entry])
 | 
			
		||||
					: []
 | 
			
		||||
			),
 | 
			
		||||
			length: 5,
 | 
			
		||||
		}),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const [submitForm, result] = useFormSubmit(form, useAliasAccountMutation());
 | 
			
		||||
	
 | 
			
		||||
	return (
 | 
			
		||||
		<form className="user-migration-alias" onSubmit={submitForm}>
 | 
			
		||||
			<div className="form-section-docs without-border">
 | 
			
		||||
				<h3>Alias Account</h3>
 | 
			
		||||
				<a
 | 
			
		||||
					href="https://docs.gotosocial.org/en/latest/user_guide/settings/#alias-account"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
					className="docslink"
 | 
			
		||||
					rel="noreferrer"
 | 
			
		||||
				>
 | 
			
		||||
					Learn more about account aliasing (opens in a new tab)
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<AlsoKnownAsURIs
 | 
			
		||||
				field={form.alsoKnownAs}
 | 
			
		||||
			/>
 | 
			
		||||
			<MutationButton
 | 
			
		||||
				disabled={false}
 | 
			
		||||
				label="Save account aliases"
 | 
			
		||||
				result={result}
 | 
			
		||||
			/>
 | 
			
		||||
		</form>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AlsoKnownAsURIs({ field: formField }) {	
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="aliases">
 | 
			
		||||
			<FormContext.Provider value={formField.ctx}>
 | 
			
		||||
				{formField.value.map((data, i) => (
 | 
			
		||||
					<AlsoKnownAsURI
 | 
			
		||||
						key={i}
 | 
			
		||||
						index={i}
 | 
			
		||||
						data={data}
 | 
			
		||||
					/>
 | 
			
		||||
				))}
 | 
			
		||||
			</FormContext.Provider>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AlsoKnownAsURI({ index, data }) {	
 | 
			
		||||
	const name = `${index}`;
 | 
			
		||||
	const form = useWithFormContext(index, {
 | 
			
		||||
		alsoKnownAsURI: useTextInput(
 | 
			
		||||
			name,
 | 
			
		||||
			// Only one field per entry.
 | 
			
		||||
			{ defaultValue: data[0] ?? "" },
 | 
			
		||||
		),
 | 
			
		||||
	}); 
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<TextInput
 | 
			
		||||
			label={`Alias #${index+1}`}
 | 
			
		||||
			field={form.alsoKnownAsURI}
 | 
			
		||||
			placeholder={`https://example.org/users/my_other_account_${index+1}`}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function MoveForm({ data: profile }) {
 | 
			
		||||
	const form = {
 | 
			
		||||
		movedToURI: useTextInput("moved_to_uri", {
 | 
			
		||||
			source: profile,
 | 
			
		||||
			valueSelector: (p) => p.moved?.uri },
 | 
			
		||||
		),
 | 
			
		||||
		password: useTextInput("password"),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const [submitForm, result] = useFormSubmit(form, useMoveAccountMutation());
 | 
			
		||||
	
 | 
			
		||||
	return (
 | 
			
		||||
		<form className="user-migration-move" onSubmit={submitForm}>
 | 
			
		||||
			<div className="form-section-docs without-border">
 | 
			
		||||
				<h3>Move Account</h3>
 | 
			
		||||
				<a
 | 
			
		||||
					href="https://docs.gotosocial.org/en/latest/user_guide/settings/#move-account"
 | 
			
		||||
					target="_blank"
 | 
			
		||||
					className="docslink"
 | 
			
		||||
					rel="noreferrer"
 | 
			
		||||
				>
 | 
			
		||||
					Learn more about moving your account (opens in a new tab)
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<TextInput
 | 
			
		||||
				field={form.movedToURI}
 | 
			
		||||
				label="Move target URI"
 | 
			
		||||
				placeholder="https://example.org/users/my_new_account"
 | 
			
		||||
			/>
 | 
			
		||||
			<TextInput
 | 
			
		||||
				type="password"
 | 
			
		||||
				name="password"
 | 
			
		||||
				field={form.password}
 | 
			
		||||
				label="Confirm account password"
 | 
			
		||||
			/>
 | 
			
		||||
			<MutationButton
 | 
			
		||||
				disabled={false}
 | 
			
		||||
				label="Confirm account move"
 | 
			
		||||
				result={result}
 | 
			
		||||
			/>
 | 
			
		||||
		</form>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -17,41 +17,41 @@
 | 
			
		||||
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
const React = require("react");
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
import {
 | 
			
		||||
	useTextInput,
 | 
			
		||||
	useFileInput,
 | 
			
		||||
	useBoolInput,
 | 
			
		||||
	useFieldArrayInput
 | 
			
		||||
} = require("../lib/form");
 | 
			
		||||
} from "../lib/form";
 | 
			
		||||
 | 
			
		||||
const useFormSubmit = require("../lib/form/submit").default;
 | 
			
		||||
const { useWithFormContext, FormContext } = require("../lib/form/context");
 | 
			
		||||
import useFormSubmit from "../lib/form/submit";
 | 
			
		||||
import { useWithFormContext, FormContext } from "../lib/form/context";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
import {
 | 
			
		||||
	TextInput,
 | 
			
		||||
	TextArea,
 | 
			
		||||
	FileInput,
 | 
			
		||||
	Checkbox
 | 
			
		||||
} = require("../components/form/inputs");
 | 
			
		||||
} from "../components/form/inputs";
 | 
			
		||||
 | 
			
		||||
const FormWithData = require("../lib/form/form-with-data").default;
 | 
			
		||||
const FakeProfile = require("../components/fake-profile");
 | 
			
		||||
const MutationButton = require("../components/form/mutation-button");
 | 
			
		||||
import FormWithData from "../lib/form/form-with-data";
 | 
			
		||||
import FakeProfile from "../components/fake-profile";
 | 
			
		||||
import MutationButton from "../components/form/mutation-button";
 | 
			
		||||
 | 
			
		||||
const { useInstanceV1Query } = require("../lib/query");
 | 
			
		||||
const { useUpdateCredentialsMutation } = require("../lib/query/user");
 | 
			
		||||
const { useVerifyCredentialsQuery } = require("../lib/query/oauth");
 | 
			
		||||
import { useInstanceV1Query } from "../lib/query";
 | 
			
		||||
import { useUpdateCredentialsMutation } from "../lib/query/user";
 | 
			
		||||
import { useVerifyCredentialsQuery } from "../lib/query/oauth";
 | 
			
		||||
 | 
			
		||||
module.exports = function UserProfile() {
 | 
			
		||||
export default function UserProfile() {
 | 
			
		||||
	return (
 | 
			
		||||
		<FormWithData
 | 
			
		||||
			dataQuery={useVerifyCredentialsQuery}
 | 
			
		||||
			DataForm={UserProfileForm}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function UserProfileForm({ data: profile }) {
 | 
			
		||||
	/*
 | 
			
		||||
@@ -91,6 +91,7 @@ function UserProfileForm({ data: profile }) {
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const [submitForm, result] = useFormSubmit(form, useUpdateCredentialsMutation(), {
 | 
			
		||||
		changedOnly: true,
 | 
			
		||||
		onFinish: () => {
 | 
			
		||||
			form.avatar.reset();
 | 
			
		||||
			form.header.reset();
 | 
			
		||||
@@ -195,7 +196,11 @@ function UserProfileForm({ data: profile }) {
 | 
			
		||||
				rows={8}
 | 
			
		||||
				disabled={!instanceConfig.allowCustomCSS}
 | 
			
		||||
			/>
 | 
			
		||||
			<MutationButton label="Save profile info" result={result} />
 | 
			
		||||
			<MutationButton
 | 
			
		||||
				disabled={false}
 | 
			
		||||
				label="Save profile info"
 | 
			
		||||
				result={result}
 | 
			
		||||
			/>
 | 
			
		||||
		</form>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -17,35 +17,28 @@
 | 
			
		||||
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
const React = require("react");
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
const query = require("../lib/query");
 | 
			
		||||
import query from "../lib/query";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
	useTextInput,
 | 
			
		||||
	useBoolInput
 | 
			
		||||
} = require("../lib/form");
 | 
			
		||||
import { useTextInput, useBoolInput } from "../lib/form";
 | 
			
		||||
 | 
			
		||||
const useFormSubmit = require("../lib/form/submit").default;
 | 
			
		||||
import useFormSubmit from "../lib/form/submit";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
	Select,
 | 
			
		||||
	TextInput,
 | 
			
		||||
	Checkbox
 | 
			
		||||
} = require("../components/form/inputs");
 | 
			
		||||
import { Select, TextInput, Checkbox } from "../components/form/inputs";
 | 
			
		||||
 | 
			
		||||
const FormWithData = require("../lib/form/form-with-data").default;
 | 
			
		||||
const Languages = require("../components/languages");
 | 
			
		||||
const MutationButton = require("../components/form/mutation-button");
 | 
			
		||||
import FormWithData from "../lib/form/form-with-data";
 | 
			
		||||
import Languages from "../components/languages";
 | 
			
		||||
import MutationButton from "../components/form/mutation-button";
 | 
			
		||||
 | 
			
		||||
module.exports = function UserSettings() {
 | 
			
		||||
export default function UserSettings() {
 | 
			
		||||
	return (
 | 
			
		||||
		<FormWithData
 | 
			
		||||
			dataQuery={query.useVerifyCredentialsQuery}
 | 
			
		||||
			DataForm={UserSettingsForm}
 | 
			
		||||
		/>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function UserSettingsForm({ data }) {
 | 
			
		||||
	/* form keys
 | 
			
		||||
@@ -94,11 +87,13 @@ function UserSettingsForm({ data }) {
 | 
			
		||||
					label="Mark my posts as sensitive by default"
 | 
			
		||||
				/>
 | 
			
		||||
 | 
			
		||||
				<MutationButton label="Save settings" result={result} />
 | 
			
		||||
				<MutationButton
 | 
			
		||||
					disabled={false}
 | 
			
		||||
					label="Save settings"
 | 
			
		||||
					result={result}
 | 
			
		||||
				/>
 | 
			
		||||
			</form>
 | 
			
		||||
			<div>
 | 
			
		||||
				<PasswordChange />
 | 
			
		||||
			</div>
 | 
			
		||||
			<PasswordChange />
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
@@ -148,7 +143,11 @@ function PasswordChange() {
 | 
			
		||||
				field={verifyNewPassword}
 | 
			
		||||
				label="Confirm new password"
 | 
			
		||||
			/>
 | 
			
		||||
			<MutationButton label="Change password" result={result} />
 | 
			
		||||
			<MutationButton
 | 
			
		||||
				disabled={false}
 | 
			
		||||
				label="Change password"
 | 
			
		||||
				result={result}
 | 
			
		||||
			/>
 | 
			
		||||
		</form>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user