Files
ncd-fe/packages/pl-fe/src/pages/settings/aliases.tsx
nicole mikołajczyk be65b924ed pl-fe: more alike input improvements
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2025-10-31 14:25:37 +01:00

193 lines
7.0 KiB
TypeScript

import clsx from 'clsx';
import React, { useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
import AccountComponent from 'pl-fe/components/account';
import Icon from 'pl-fe/components/icon';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Button from 'pl-fe/components/ui/button';
import { CardHeader, CardTitle } from 'pl-fe/components/ui/card';
import Column from 'pl-fe/components/ui/column';
import HStack from 'pl-fe/components/ui/hstack';
import IconButton from 'pl-fe/components/ui/icon-button';
import Text from 'pl-fe/components/ui/text';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useSearchAccounts } from 'pl-fe/queries/search/use-search';
import { useAccountAliases, useAddAccountAlias, useDeleteAccountAlias } from 'pl-fe/queries/settings/use-account-aliases';
const messages = defineMessages({
heading: { id: 'column.aliases', defaultMessage: 'Account aliases' },
subheading_add_new: { id: 'column.aliases.subheading_add_new', defaultMessage: 'Add New Alias' },
create_error: { id: 'column.aliases.create_error', defaultMessage: 'Error creating alias' },
delete_error: { id: 'column.aliases.delete_error', defaultMessage: 'Error deleting alias' },
subheading_aliases: { id: 'column.aliases.subheading_aliases', defaultMessage: 'Current aliases' },
delete: { id: 'column.aliases.delete', defaultMessage: 'Delete' },
add: { id: 'aliases.account.add', defaultMessage: 'Create alias' },
search: { id: 'aliases.search', defaultMessage: 'Search your old account' },
searchTitle: { id: 'tabs_bar.search', defaultMessage: 'Search' },
clear: { id: 'search.clear', defaultMessage: 'Clear input' },
});
interface IAccount {
accountId: string;
aliases: string[];
}
const Account: React.FC<IAccount> = ({ accountId, aliases }) => {
const intl = useIntl();
const features = useFeatures();
const me = useAppSelector((state) => state.me);
const { account } = useAccount(accountId);
const { mutate: addAccountAlias } = useAddAccountAlias();
const apId = account?.ap_id;
const name = features.accountMoving ? account?.acct : apId;
const added = name ? aliases.includes(name) : false;
const handleOnAdd = () => addAccountAlias(name!);
if (!account) return null;
let button;
if (!added && accountId !== me) {
button = (
<IconButton src={require('@phosphor-icons/core/regular/plus.svg')} className='text-gray-400 hover:text-gray-600' iconClassName='h-5 w-5' title={intl.formatMessage(messages.add)} onClick={handleOnAdd} />
);
}
return (
<HStack space={1} alignItems='center' justifyContent='between' className='p-2.5'>
<div className='w-full'>
<AccountComponent account={account} withRelationship={false} />
</div>
{button}
</HStack>
);
};
interface IAliasesSearch {
onSubmit: (value: string) => void;
}
const Search: React.FC<IAliasesSearch> = ({ onSubmit }) => {
const intl = useIntl();
const [value, setValue] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.keyCode === 13) {
onSubmit(value);
}
};
const handleSubmit = () => {
onSubmit(value);
};
const handleClear = () => {
setValue('');
onSubmit('');
};
const hasValue = value.length > 0;
return (
<div className='flex items-center gap-1'>
<label className='relative grow'>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
<input
className='block w-full rounded-full focus:border-primary-500 focus:ring-primary-500 dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500 sm:text-sm'
type='text'
value={value}
onChange={handleChange}
onKeyUp={handleKeyUp}
placeholder={intl.formatMessage(messages.search)}
/>
<button
disabled={!hasValue}
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto'
onClick={handleClear}
title={intl.formatMessage(messages.clear)}
>
<Icon src={require('@phosphor-icons/core/regular/backspace.svg')} className={clsx('size-5 text-gray-600', { 'hidden': !hasValue })} />
</button>
</label>
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
</div>
);
};
const AliasesPage = () => {
const intl = useIntl();
const [query, setQuery] = useState('');
const { data: aliases = [] } = useAccountAliases();
const { data: searchAccountIds = [], isFetched } = useSearchAccounts(query);
const { mutate: deleteAccountAlias } = useDeleteAccountAlias();
const handleAliasDelete: React.MouseEventHandler<HTMLButtonElement> = e => {
deleteAccountAlias(e.currentTarget.dataset.value as string);
};
const emptyMessage = <FormattedMessage id='empty_column.aliases' defaultMessage="You haven't created any account alias yet." />;
return (
<Column className='flex-1' label={intl.formatMessage(messages.heading)}>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.subheading_add_new)} />
</CardHeader>
<Search onSubmit={setQuery} />
{
isFetched && searchAccountIds.length === 0 ? (
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' />
</div>
) : (
<div className='mb-4 max-h-72 overflow-y-auto'>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} aliases={aliases} />)}
</div>
)
}
<CardHeader>
<CardTitle title={intl.formatMessage(messages.subheading_aliases)} />
</CardHeader>
<div className='flex-1'>
<ScrollableList
scrollKey='aliases'
emptyMessageText={emptyMessage}
>
{aliases.map((alias, i) => (
<HStack alignItems='center' justifyContent='between' space={1} key={i} className='p-2'>
<div>
<Text tag='span' theme='muted'><FormattedMessage id='aliases.account_label' defaultMessage='Old account:' /></Text>
{' '}
<Text tag='span'>{alias}</Text>
</div>
<button onClick={handleAliasDelete} data-value={alias} aria-label={intl.formatMessage(messages.delete)}>
<Text theme='muted' className='flex items-center gap-1'>
<Icon src={require('@phosphor-icons/core/regular/x.svg')} />
<FormattedMessage id='aliases.aliases_list_delete' defaultMessage='Unlink alias' />
</Text>
</button>
</HStack>
))}
</ScrollableList>
</div>
</Column>
);
};
export { AliasesPage as default };