Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿

feat: add group selector to composer (#5915)

authored by yoginth.com and committed by

GitHub c22712e5 82c51942

+164 -2
+88
apps/web/src/components/Composer/Actions/GroupFeedSelector.tsx
··· 1 + import { GlobeAltIcon } from "@heroicons/react/24/outline"; 2 + import getAvatar from "@hey/helpers/getAvatar"; 3 + import { 4 + type GroupFragment, 5 + GroupsOrderBy, 6 + type GroupsRequest, 7 + PageSize, 8 + useGroupsQuery 9 + } from "@hey/indexer"; 10 + import { useMemo, useState } from "react"; 11 + import { Modal, Tooltip } from "@/components/Shared/UI"; 12 + import { useAccountStore } from "@/store/persisted/useAccountStore"; 13 + import GroupSelector from "../GroupSelector"; 14 + 15 + interface GroupFeedSelectorProps { 16 + selected?: string; 17 + onChange: (feed: string) => void; 18 + } 19 + 20 + const GroupFeedSelector = ({ selected, onChange }: GroupFeedSelectorProps) => { 21 + const { currentAccount } = useAccountStore(); 22 + const [showModal, setShowModal] = useState(false); 23 + 24 + const request: GroupsRequest = { 25 + filter: { member: currentAccount?.address }, 26 + orderBy: GroupsOrderBy.LatestFirst, 27 + pageSize: PageSize.Fifty 28 + }; 29 + 30 + const { data } = useGroupsQuery({ 31 + skip: !currentAccount, 32 + variables: { request } 33 + }); 34 + 35 + const groups = useMemo( 36 + () => data?.groups?.items ?? [], 37 + [data?.groups?.items] 38 + ); 39 + 40 + const selectedGroup = useMemo( 41 + () => 42 + groups.find((group: GroupFragment) => group.feed?.address === selected), 43 + [groups, selected] 44 + ); 45 + 46 + const handleChange = (feed: string) => { 47 + onChange(feed); 48 + setShowModal(false); 49 + }; 50 + 51 + return ( 52 + <> 53 + <Tooltip content="Post to" placement="top" withDelay> 54 + <button 55 + aria-label="Select group" 56 + className="rounded-full outline-offset-8" 57 + disabled={!groups.length} 58 + onClick={() => setShowModal(true)} 59 + type="button" 60 + > 61 + {selectedGroup ? ( 62 + <img 63 + alt={selectedGroup.metadata?.name ?? selectedGroup.address} 64 + className="size-5 rounded-md" 65 + src={getAvatar(selectedGroup)} 66 + /> 67 + ) : ( 68 + <GlobeAltIcon className="size-5" /> 69 + )} 70 + </button> 71 + </Tooltip> 72 + <Modal 73 + onClose={() => setShowModal(false)} 74 + show={showModal} 75 + title="Select group" 76 + > 77 + <div className="space-y-3 p-5"> 78 + <p className="text-gray-500 text-sm"> 79 + Posts will appear on your profile feed if no group is selected. 80 + </p> 81 + <GroupSelector onChange={handleChange} selected={selected} /> 82 + </div> 83 + </Modal> 84 + </> 85 + ); 86 + }; 87 + 88 + export default GroupFeedSelector;
+57
apps/web/src/components/Composer/GroupSelector.tsx
··· 1 + import getAvatar from "@hey/helpers/getAvatar"; 2 + import { 3 + type GroupFragment, 4 + GroupsOrderBy, 5 + type GroupsRequest, 6 + PageSize, 7 + useGroupsQuery 8 + } from "@hey/indexer"; 9 + import { useMemo } from "react"; 10 + import { Select } from "@/components/Shared/UI"; 11 + import { useAccountStore } from "@/store/persisted/useAccountStore"; 12 + 13 + interface GroupSelectorProps { 14 + selected?: string; 15 + onChange: (groupFeed: string) => void; 16 + } 17 + 18 + const GroupSelector = ({ selected, onChange }: GroupSelectorProps) => { 19 + const { currentAccount } = useAccountStore(); 20 + 21 + const request: GroupsRequest = { 22 + filter: { member: currentAccount?.address }, 23 + orderBy: GroupsOrderBy.LatestFirst, 24 + pageSize: PageSize.Fifty 25 + }; 26 + 27 + const { data } = useGroupsQuery({ 28 + skip: !currentAccount, 29 + variables: { request } 30 + }); 31 + 32 + const options = useMemo(() => { 33 + const groups = data?.groups?.items ?? []; 34 + return groups.map((group: GroupFragment) => ({ 35 + icon: getAvatar(group), 36 + label: group.metadata?.name ?? group.address, 37 + selected: group.feed?.address === selected, 38 + value: group.feed?.address ?? "" 39 + })); 40 + }, [data?.groups?.items, selected]); 41 + 42 + if (!options.length) { 43 + return null; 44 + } 45 + 46 + return ( 47 + <Select 48 + className="w-full" 49 + iconClassName="size-5 rounded-md" 50 + onChange={(value) => onChange(value as string)} 51 + options={options as any} 52 + showSearch 53 + /> 54 + ); 55 + }; 56 + 57 + export default GroupSelector;
+19 -2
apps/web/src/components/Composer/NewPublication.tsx
··· 42 42 usePostVideoStore 43 43 } from "@/store/non-persisted/post/usePostVideoStore"; 44 44 import { useAccountStore } from "@/store/persisted/useAccountStore"; 45 + import GroupFeedSelector from "./Actions/GroupFeedSelector"; 45 46 import { Editor, useEditorContext, withEditorContext } from "./Editor"; 46 47 import LinkPreviews from "./LinkPreviews"; 47 48 ··· 95 96 const [isSubmitting, setIsSubmitting] = useState(false); 96 97 const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false); 97 98 const [postContentError, setPostContentError] = useState(""); 99 + const [selectedFeed, setSelectedFeed] = useState<string>(feed || ""); 98 100 99 101 const editor = useEditorContext(); 100 102 const getMetadata = usePostMetadata(); ··· 118 120 setAudioPost(DEFAULT_AUDIO_POST); 119 121 setLicense(null); 120 122 resetCollectSettings(); 123 + setSelectedFeed(feed || ""); 121 124 setShowNewPostModal(false); 122 125 }; 123 126 ··· 142 145 }); 143 146 144 147 useEffect(() => { 148 + setSelectedFeed(feed || ""); 149 + }, [feed]); 150 + 151 + useEffect(() => { 145 152 setPostContentError(""); 146 153 }, [audioPost]); 147 154 ··· 215 222 variables: { 216 223 request: { 217 224 contentUri, 218 - ...(feed && { feed }), 225 + ...((feed || selectedFeed) && { feed: feed || selectedFeed }), 219 226 ...(isComment && { commentOn: { post: post?.id } }), 220 227 ...(isQuote && { quoteOf: { post: quotedPost?.id } }), 221 228 ...(collectAction.enabled && { ··· 277 284 <> 278 285 <CollectSettings /> 279 286 <RulesSettings /> 280 - {isComment ? null : <LivestreamSettings />} 287 + {isComment ? null : ( 288 + <> 289 + <LivestreamSettings /> 290 + {feed ? null : ( 291 + <GroupFeedSelector 292 + onChange={setSelectedFeed} 293 + selected={selectedFeed} 294 + /> 295 + )} 296 + </> 297 + )} 281 298 </> 282 299 )} 283 300 </div>