diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7a145b2140..93d2c4a3c1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -71,6 +71,11 @@ module.exports = { message: 'Please use the useTypedReset() hook instead of importing reset from @react-navigation/native for type safety.', }, + { + selector: 'ImportDeclaration > Literal[value=/^packages/]', + message: + 'Do not import directly from the "packages" directory. Use the package name (or relative path, if within the same package) instead.', + }, ], }, }; diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index 38819cac2a..1b3c2d214d 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -14,6 +14,7 @@ import { ChatOptionsProvider, ChatOptionsSheet, ChatOptionsSheetMethods, + GroupPreviewAction, GroupPreviewSheet, InviteUsersSheet, NavBarView, @@ -27,6 +28,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { TLON_EMPLOYEE_GROUP } from '../../constants'; import { useChatSettingsNavigation } from '../../hooks/useChatSettingsNavigation'; import { useCurrentUserId } from '../../hooks/useCurrentUser'; +import { useGroupActions } from '../../hooks/useGroupActions'; import { useFeatureFlag } from '../../lib/featureFlags'; import type { RootStackParamList } from '../../navigation/types'; import { screenNameFromChannelId } from '../../navigation/utils'; @@ -91,6 +93,7 @@ export function ChatListScreenView({ const { data: chats } = store.useCurrentChats({ enabled: isFocused, }); + const { performGroupAction } = useGroupActions(); const currentUser = useCurrentUserId(); @@ -286,6 +289,14 @@ export function ChatListScreenView({ setShowSearchInput(!showSearchInput); }, [showSearchInput]); + const handleGroupAction = useCallback( + (action: GroupPreviewAction, group: db.Group) => { + performGroupAction(action, group); + setSelectedGroupId(null); + }, + [performGroupAction] + ); + return ( (null); const resetToDm = useResetToDm(); + const { performGroupAction } = useGroupActions(); const handleGoToDm = useCallback( async (participants: string[]) => { @@ -43,6 +46,14 @@ export function UserProfileScreen({ route: { params }, navigation }: Props) { navigation.push('EditProfile'); }, [navigation]); + const handleGroupAction = useCallback( + (action: GroupPreviewAction, group: db.Group) => { + setSelectedGroup(null); + performGroupAction(action, group); + }, + [performGroupAction] + ); + return ( diff --git a/packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx b/packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx index 82d8f2b55d..e4ba7753a8 100644 --- a/packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx +++ b/packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx @@ -45,6 +45,7 @@ export default Object.fromEntries( open={true} onOpenChange={() => {}} group={group} + onActionComplete={() => {}} />, ]; }) diff --git a/packages/app/hooks/useGroupActions.tsx b/packages/app/hooks/useGroupActions.tsx index fa97703023..f00ae8945a 100644 --- a/packages/app/hooks/useGroupActions.tsx +++ b/packages/app/hooks/useGroupActions.tsx @@ -5,24 +5,19 @@ import { useCallback } from 'react'; import { useGroupNavigation } from './useGroupNavigation'; export const useGroupActions = () => { - const { goToChannel, goToHome } = useGroupNavigation(); + const { goToHome, goToGroupChannels } = useGroupNavigation(); const performGroupAction = useCallback( async (action: GroupPreviewAction, updatedGroup: db.Group) => { - if (action === 'goTo' && updatedGroup.lastPost?.channelId) { - const channel = await db.getChannel({ - id: updatedGroup.lastPost.channelId, - }); - if (channel) { - goToChannel(channel); - } + if (action === 'goTo') { + goToGroupChannels(updatedGroup.id); } if (action === 'joined') { goToHome(); } }, - [goToChannel, goToHome] + [goToGroupChannels, goToHome] ); return { diff --git a/packages/app/hooks/useGroupNavigation.ts b/packages/app/hooks/useGroupNavigation.ts index ac7a5e7a6e..e62d7998c4 100644 --- a/packages/app/hooks/useGroupNavigation.ts +++ b/packages/app/hooks/useGroupNavigation.ts @@ -3,12 +3,14 @@ import * as db from '@tloncorp/shared/db'; import { useCallback } from 'react'; import { RootStackParamList } from '../navigation/types'; +import { useResetToGroup } from '../navigation/utils'; export const useGroupNavigation = () => { const navigation = useNavigation< // @ts-expect-error - TODO: pass navigation handlers into context NativeStackNavigationProp >(); + const resetToGroup = useResetToGroup(); const goToChannel = useCallback( async ( @@ -24,6 +26,13 @@ export const useGroupNavigation = () => { [navigation] ); + const goToGroupChannels = useCallback( + async (groupId: string) => { + resetToGroup(groupId); + }, + [resetToGroup] + ); + const goToHome = useCallback(() => { navigation.navigate('ChatList'); }, [navigation]); @@ -39,5 +48,6 @@ export const useGroupNavigation = () => { goToChannel, goToHome, goToContactHostedGroups, + goToGroupChannels, }; }; diff --git a/packages/app/navigation/utils.ts b/packages/app/navigation/utils.ts index aa2b6c68a1..13348316aa 100644 --- a/packages/app/navigation/utils.ts +++ b/packages/app/navigation/utils.ts @@ -80,6 +80,17 @@ export function useResetToDm() { }; } +export function useResetToGroup() { + const reset = useTypedReset(); + + return function resetToGroup(groupId: string) { + reset([ + { name: 'ChatList' }, + { name: 'GroupChannels', params: { groupId } }, + ]); + }; +} + export function screenNameFromChannelId(channelId: string) { return logic.isDmChannelId(channelId) ? 'DM' diff --git a/packages/app/package.json b/packages/app/package.json index 1fd3d95f92..90e38ca9b7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -4,6 +4,9 @@ "main": "index.ts", "private": true, "scripts": { + "lint": "eslint ./", + "lint:fix": "eslint ./ --fix --quiet", + "lint:format": "prettier ./ --write", "test": "vitest --watch false" }, "dependencies": { diff --git a/packages/ui/src/components/ContentReference/ContentReference.tsx b/packages/ui/src/components/ContentReference/ContentReference.tsx index 7ea9599d7d..f0d9b777ca 100644 --- a/packages/ui/src/components/ContentReference/ContentReference.tsx +++ b/packages/ui/src/components/ContentReference/ContentReference.tsx @@ -2,7 +2,7 @@ import { ContentReference } from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; import { getChannelType } from '@tloncorp/shared/urbit'; -import React, { useState } from 'react'; +import React from 'react'; import { ComponentProps, useCallback } from 'react'; import { View, XStack, styled } from 'tamagui'; @@ -11,7 +11,6 @@ import { useRequests } from '../../contexts/requests'; import { ContactAvatar, GroupAvatar } from '../Avatar'; import { useContactName } from '../ContactNameV2'; import { GalleryContentRenderer } from '../GalleryPost'; -import { GroupPreviewSheet } from '../GroupPreviewSheet'; import { IconType } from '../Icon'; import { ListItem } from '../ListItem'; import { useBoundHandler } from '../ListItem/listItemUtils'; @@ -76,7 +75,6 @@ export function PostReferenceLoader({ postId: string; replyId?: string; }) { - const [selectedGroup, setSelectedGroup] = useState(null); const { usePostReference, useChannel, useGroup } = useRequests(); const postQuery = usePostReference({ postId: replyId ? replyId : postId, @@ -84,20 +82,14 @@ export function PostReferenceLoader({ }); const { data: channel } = useChannel({ id: channelId }); const { data: group } = useGroup(channel?.groupId ?? ''); - const { onPressRef } = useNavigation(); + const { onPressRef, onPressGroupRef } = useNavigation(); const handlePress = useCallback(async () => { if (channel && postQuery.data && group && group.currentUserIsMember) { onPressRef?.(channel, postQuery.data); } else if (group) { - setSelectedGroup(group ?? null); + onPressGroupRef?.(group); } - }, [channel, onPressRef, postQuery.data, group]); - - const handleGroupPreviewSheetOpenChange = useCallback((open: boolean) => { - if (!open) { - setSelectedGroup(null); - } - }, []); + }, [channel, onPressRef, postQuery.data, group, onPressGroupRef]); return ( <> @@ -111,12 +103,6 @@ export function PostReferenceLoader({ onPress={openOnPress ? handlePress : undefined} {...props} /> - - ); } diff --git a/packages/ui/src/components/GroupPreviewSheet.tsx b/packages/ui/src/components/GroupPreviewSheet.tsx index b457370a20..e4c16b48e4 100644 --- a/packages/ui/src/components/GroupPreviewSheet.tsx +++ b/packages/ui/src/components/GroupPreviewSheet.tsx @@ -16,7 +16,7 @@ import { LoadingSpinner } from './LoadingSpinner'; interface Props { open: boolean; onOpenChange: (open: boolean) => void; - onActionComplete?: (action: GroupPreviewAction, group: db.Group) => void; + onActionComplete: (action: GroupPreviewAction, group: db.Group) => void; group?: db.Group; } @@ -44,7 +44,11 @@ function GroupPreviewSheetComponent({ const actionHandler = useCallback( (action: GroupPreviewAction, updatedGroup: db.Group) => { - onActionComplete?.(action, updatedGroup); + // Delay the action complete callback to allow the sheet to close. + // If we don't do this the app will crash. + setTimeout(() => { + onActionComplete?.(action, updatedGroup); + }, 100); onOpenChange(false); }, [onActionComplete, onOpenChange]