diff --git a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css --- a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css +++ b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css @@ -1,6 +1,6 @@ .inputLabel { color: var(--text-background-primary-default); - margin-bottom: 8px; + margin-bottom: 16px; } .dropdownInputContainer { @@ -9,7 +9,24 @@ align-items: center; } +.textInputContainer { + margin: 0 32px; +} + +.tagFarcasterChannelByNameContainer { + display: flex; + flex-direction: column; + justify-content: flex-end; + margin-top: 24px; + visibility: hidden; +} + +.tagFarcasterChannelByNameContainerVisible { + visibility: visible; +} + .errorMessage { + margin-top: 4px; text-align: center; color: var(--text-background-danger-default); font-size: var(--s-font-14); diff --git a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js --- a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js +++ b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js @@ -15,6 +15,7 @@ import css from './create-farcaster-channel-tag-modal.css'; import Button, { buttonThemes } from '../components/button.react.js'; import Dropdown, { type DropdownOption } from '../components/dropdown.react.js'; +import Input from '../modals/input.react.js'; import Modal from '../modals/modal.react.js'; type Props = { @@ -38,6 +39,7 @@ $ReadOnlyArray, >([]); const [selectedOption, setSelectedOption] = React.useState(null); + const [channelNameText, setChannelNameText] = React.useState(''); const [error, setError] = React.useState(null); React.useEffect(() => { @@ -51,30 +53,56 @@ name: `/${channel.id}`, })); - setChannelOptions(sortedChannels); + const options = [{ id: 'other', name: 'Other' }, ...sortedChannels]; + + setChannelOptions(options); })(); }, [client, fid]); const onChangeSelectedOption = React.useCallback((option: string) => { setError(null); + setChannelNameText(''); setSelectedOption(option); }, []); + const onChangeChannelNameText = React.useCallback( + (event: SyntheticEvent) => { + setChannelNameText(event.currentTarget.value); + }, + [], + ); + const { createTag, isLoading } = useCreateFarcasterChannelTag( communityID, setError, popModal, ); - const onClickTagChannel = React.useCallback(() => { + const onClickTagChannel = React.useCallback(async () => { if (!selectedOption) { return; } - createTag(selectedOption); - }, [createTag, selectedOption]); + if (selectedOption !== 'other') { + createTag(selectedOption); + return; + } - const buttonDisabled = isLoading || !selectedOption; + const channelInfo = + await neynarClientContext.client.fetchFarcasterChannelByName( + channelNameText, + ); + if (!channelInfo) { + setError('channel_not_found'); + return; + } + createTag(channelInfo.id); + }, [channelNameText, createTag, neynarClientContext.client, selectedOption]); + + const buttonDisabled = + isLoading || + !selectedOption || + (selectedOption === 'other' && !channelNameText); const primaryButton = React.useMemo(() => { return ( @@ -89,6 +117,14 @@ ); }, [onClickTagChannel, buttonDisabled]); + const tagFarcasterChannelByNameContainerClassName = classNames( + css.tagFarcasterChannelByNameContainer, + { + [css.tagFarcasterChannelByNameContainerVisible]: + selectedOption === 'other', + }, + ); + const errorMessageClassName = classNames(css.errorMessage, { [css.errorMessageVisible]: error, }); @@ -115,17 +151,30 @@ setActiveSelection={onChangeSelectedOption} /> +
+
Channel name
+
+ +
+
{errorMessage}
), [ + channelNameText, channelOptions, errorMessage, errorMessageClassName, + onChangeChannelNameText, onChangeSelectedOption, popModal, primaryButton, selectedOption, + tagFarcasterChannelByNameContainerClassName, ], );