Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F2901669
D12566.id41912.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
D12566.id41912.diff
View Options
diff --git a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css
new file mode 100644
--- /dev/null
+++ b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.css
@@ -0,0 +1,21 @@
+.inputLabel {
+ color: var(--text-background-primary-default);
+ margin-bottom: 8px;
+}
+
+.dropdownInputContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.errorMessage {
+ text-align: center;
+ color: var(--text-background-danger-default);
+ font-size: var(--s-font-14);
+ visibility: hidden;
+}
+
+.errorMessageVisible {
+ visibility: visible;
+}
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
new file mode 100644
--- /dev/null
+++ b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js
@@ -0,0 +1,135 @@
+// @flow
+
+import classNames from 'classnames';
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js';
+import {
+ tagFarcasterChannelErrorMessages,
+ useCreateFarcasterChannelTag,
+} from 'lib/shared/community-utils.js';
+import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js';
+
+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 Modal from '../modals/modal.react.js';
+
+type Props = {
+ +communityID: string,
+};
+
+function CreateFarcasterChannelTagModal(props: Props): React.Node {
+ const { communityID } = props;
+
+ const { popModal } = useModalContext();
+
+ const fid = useCurrentUserFID();
+ invariant(fid, 'FID should be set');
+
+ const neynarClientContext = React.useContext(NeynarClientContext);
+ invariant(neynarClientContext, 'NeynarClientContext is missing');
+
+ const { client } = neynarClientContext;
+
+ const [channelOptions, setChannelOptions] = React.useState<
+ $ReadOnlyArray<DropdownOption>,
+ >([]);
+ const [selectedOption, setSelectedOption] = React.useState<?string>(null);
+ const [error, setError] = React.useState<?string>(null);
+
+ React.useEffect(() => {
+ void (async () => {
+ const channels = await client.fetchFollowedFarcasterChannels(fid);
+
+ const sortedChannels = channels
+ .sort((a, b) => a.id.localeCompare(b.id))
+ .map(channel => ({
+ id: channel.id,
+ name: `/${channel.id}`,
+ }));
+
+ setChannelOptions(sortedChannels);
+ })();
+ }, [client, fid]);
+
+ const onChangeSelectedOption = React.useCallback((option: string) => {
+ setError(null);
+ setSelectedOption(option);
+ }, []);
+
+ const { createTag, isLoading } = useCreateFarcasterChannelTag(
+ communityID,
+ setError,
+ popModal,
+ );
+
+ const onClickTagChannel = React.useCallback(() => {
+ if (!selectedOption) {
+ return;
+ }
+
+ createTag(selectedOption);
+ }, [createTag, selectedOption]);
+
+ const buttonDisabled = isLoading || !selectedOption;
+
+ const primaryButton = React.useMemo(() => {
+ return (
+ <Button
+ variant="filled"
+ buttonColor={buttonThemes.standard}
+ onClick={onClickTagChannel}
+ disabled={buttonDisabled}
+ >
+ Create tag
+ </Button>
+ );
+ }, [onClickTagChannel, buttonDisabled]);
+
+ const errorMessageClassName = classNames(css.errorMessage, {
+ [css.errorMessageVisible]: error,
+ });
+
+ const errorMessage =
+ error && tagFarcasterChannelErrorMessages[error]
+ ? tagFarcasterChannelErrorMessages[error]
+ : 'Unknown error.';
+
+ const createFarcasterChannelTagModal = React.useMemo(
+ () => (
+ <Modal
+ name="Create a tag"
+ onClose={popModal}
+ size="large"
+ primaryButton={primaryButton}
+ >
+ <div className={css.inputLabel}>Farcaster channel</div>
+ <div className={css.dropdownInputContainer}>
+ <Dropdown
+ options={channelOptions}
+ defaultLabel="Select a channel"
+ activeSelection={selectedOption}
+ setActiveSelection={onChangeSelectedOption}
+ />
+ </div>
+ <div className={errorMessageClassName}>{errorMessage}</div>
+ </Modal>
+ ),
+ [
+ channelOptions,
+ errorMessage,
+ errorMessageClassName,
+ onChangeSelectedOption,
+ popModal,
+ primaryButton,
+ selectedOption,
+ ],
+ );
+
+ return createFarcasterChannelTagModal;
+}
+
+export default CreateFarcasterChannelTagModal;
diff --git a/web/tag-farcaster-channel/tag-farcaster-channel-modal.react.js b/web/tag-farcaster-channel/tag-farcaster-channel-modal.react.js
--- a/web/tag-farcaster-channel/tag-farcaster-channel-modal.react.js
+++ b/web/tag-farcaster-channel/tag-farcaster-channel-modal.react.js
@@ -10,8 +10,10 @@
} from 'lib/shared/community-utils.js';
import type { CommunityInfo } from 'lib/types/community-types.js';
+import CreateFarcasterChannelTagModal from './create-farcaster-channel-tag-modal.react.js';
import RemoveTagButton from './remove-tag-button.react.js';
import css from './tag-farcaster-channel-modal.css';
+import Button, { buttonThemes } from '../components/button.react.js';
import Modal from '../modals/modal.react.js';
import { useSelector } from '../redux/redux-utils.js';
@@ -22,7 +24,7 @@
function TagFarcasterChannelModal(props: Props): React.Node {
const { communityID } = props;
- const { popModal } = useModalContext();
+ const { popModal, pushModal } = useModalContext();
const communityInfo: ?CommunityInfo = useSelector(
state => state.communityStore.communityInfos[communityID],
@@ -30,6 +32,12 @@
const [removeTagError, setRemoveTagError] = React.useState<?string>();
+ const openCreateFarcasterChannelTagModal = React.useCallback(
+ () =>
+ pushModal(<CreateFarcasterChannelTagModal communityID={communityID} />),
+ [communityID, pushModal],
+ );
+
const channelNameTextContent = React.useMemo(() => {
if (!communityInfo?.farcasterChannelID) {
return (
@@ -56,9 +64,21 @@
/>
);
}
- // TODO: Implement TagChannelButton
- return null;
- }, [communityID, communityInfo?.farcasterChannelID]);
+
+ return (
+ <Button
+ variant="filled"
+ buttonColor={buttonThemes.standard}
+ onClick={openCreateFarcasterChannelTagModal}
+ >
+ Tag channel
+ </Button>
+ );
+ }, [
+ communityID,
+ communityInfo?.farcasterChannelID,
+ openCreateFarcasterChannelTagModal,
+ ]);
const errorMessageClassName = classNames(css.errorMessage, {
[css.errorMessageVisible]: removeTagError,
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Oct 6, 2:02 PM (20 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2249847
Default Alt Text
D12566.id41912.diff (6 KB)
Attached To
Mode
D12566: [web] introduce CreateFarcasterChannelTagModal
Attached
Detach File
Event Timeline
Log In to Comment