mirror of
https://github.com/hanzo-io/chat.git
synced 2026-10-30 18:57:58 +00:00
167 lines
7.3 KiB
TypeScript
167 lines
7.3 KiB
TypeScript
import { View, Text, Alert, Pressable } from 'react-native';
|
|
import { useFrappeDeleteDoc, useFrappeGetCall, useFrappeUpdateDoc, useSWRConfig } from 'frappe-react-sdk';
|
|
import { useLocalSearchParams } from 'expo-router';
|
|
import Reanimated, { SharedValue, useAnimatedStyle } from 'react-native-reanimated';
|
|
import { Member, useFetchChannelMembers } from '@raven/lib/hooks/useFetchChannelMembers';
|
|
import UserAvatar from '@components/layout/UserAvatar';
|
|
import { Button } from '@components/nativewindui/Button';
|
|
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
|
|
import TrashIcon from "@assets/icons/TrashIcon.svg"
|
|
import CrownIcon from "@assets/icons/CrownIcon.svg"
|
|
import useCurrentRavenUser from '@raven/lib/hooks/useCurrentRavenUser';
|
|
import { useActionSheet } from '@expo/react-native-action-sheet';
|
|
import { useCurrentChannelData } from '@hooks/useCurrentChannelData';
|
|
import { toast } from 'sonner-native';
|
|
import { ActivityIndicator } from '@components/nativewindui/ActivityIndicator';
|
|
import { COLORS } from '@theme/colors';
|
|
|
|
const ChannelMemberRow = ({ member }: { member: Member }) => {
|
|
|
|
const { id: channelId } = useLocalSearchParams()
|
|
const { myProfile: currentUserInfo } = useCurrentRavenUser()
|
|
const { channelMembers, mutate: updateMembers } = useFetchChannelMembers(channelId as string ?? "")
|
|
const { updateDoc, loading: updatingMember, reset } = useFrappeUpdateDoc()
|
|
const { channel } = useCurrentChannelData(channelId as string ?? "")
|
|
const isAllowed = channelMembers[currentUserInfo?.name ?? ""]?.is_admin === 1 && member.name !== currentUserInfo?.name && channel?.channelData.type !== "Open" && channel?.channelData.is_archived === 0
|
|
const isBot = member.type === "Bot"
|
|
|
|
const { data: memberInfo } = useFrappeGetCall<{ message: { name: string } }>("frappe.client.get_value", {
|
|
doctype: "Raven Channel Member",
|
|
filters: JSON.stringify({ channel_id: channelId, user_id: member?.name }),
|
|
fieldname: JSON.stringify(["name"]),
|
|
}, undefined, {
|
|
revalidateOnFocus: false
|
|
})
|
|
|
|
const updateAdminStatus = async (admin: 1 | 0) => {
|
|
if (isBot) {
|
|
toast.error("Bots cannot be made admins")
|
|
return
|
|
}
|
|
return updateDoc("Raven Channel Member", memberInfo?.message.name ?? "", {
|
|
is_admin: admin,
|
|
}).then(() => {
|
|
updateMembers()
|
|
reset()
|
|
if (admin === 1) {
|
|
toast.success(`${member.full_name} has been made an admin`)
|
|
} else {
|
|
toast.warning(`${member.full_name} is no longer an admin`)
|
|
}
|
|
}).catch((e) => {
|
|
toast.error("Failed to update member status")
|
|
reset()
|
|
})
|
|
}
|
|
|
|
function RightAction(prog: SharedValue<number>, drag: SharedValue<number>, member: Member) {
|
|
|
|
const styleAnimation = useAnimatedStyle(() => {
|
|
return {
|
|
transform: [{ translateX: drag.value + 70 }],
|
|
}
|
|
})
|
|
|
|
const { deleteDoc, error, loading: deletingDoc } = useFrappeDeleteDoc()
|
|
const { mutate } = useSWRConfig()
|
|
|
|
const { data: memberInfo, error: errorFetchingChannelMember } = useFrappeGetCall<{ message: { name: string } }>('frappe.client.get_value', {
|
|
doctype: "Raven Channel Member",
|
|
filters: JSON.stringify({ channel_id: channelId, user_id: member?.name }),
|
|
fieldname: JSON.stringify(["name"])
|
|
}, undefined, {
|
|
revalidateOnFocus: false
|
|
})
|
|
|
|
const deleteMember = async () => {
|
|
return deleteDoc('Raven Channel Member', memberInfo?.message.name).then(() => {
|
|
toast.success(`Removed ${member.full_name} from the channel`)
|
|
mutate(["channel_members", channelId])
|
|
})
|
|
}
|
|
|
|
const showAlert = () =>
|
|
Alert.alert(
|
|
`Remove Member?`,
|
|
`${member.full_name} will no longer have access to ${channel?.channelData.channel_name} channel.`,
|
|
[
|
|
{
|
|
text: 'Cancel',
|
|
},
|
|
{
|
|
text: 'Remove',
|
|
style: 'destructive',
|
|
onPress: deleteMember
|
|
},
|
|
]
|
|
)
|
|
|
|
return (
|
|
<Reanimated.View style={styleAnimation}>
|
|
<Button variant="plain" size="none" disabled={!isAllowed || deletingDoc} onPress={showAlert} style={{ width: 70, height: "100%" }} className={`bg-red-500 dark:bg-red-600 rounded-none items-center justify-center ${!isAllowed ? 'opacity-45' : ''}`}>
|
|
{deletingDoc ? <ActivityIndicator size="small" color={COLORS.white} /> : <TrashIcon width={22} height={22} fill="white" />}
|
|
</Button>
|
|
</Reanimated.View>
|
|
)
|
|
}
|
|
|
|
const { showActionSheetWithOptions } = useActionSheet()
|
|
|
|
const showActions = () => {
|
|
|
|
let options = ['Make channel admin', 'Dismiss channel admin', 'Cancel']
|
|
const isAdmin = channelMembers[member.name].is_admin
|
|
|
|
if (isAllowed) {
|
|
showActionSheetWithOptions({
|
|
options,
|
|
cancelButtonIndex: 2,
|
|
disabledButtonIndices: isAdmin ? [0] : [1],
|
|
destructiveButtonIndex: isAdmin ? 1 : undefined,
|
|
}, async (selectedIndex: number | undefined) => {
|
|
switch (selectedIndex) {
|
|
case 0:
|
|
await updateAdminStatus(1)
|
|
break
|
|
case 1:
|
|
await updateAdminStatus(0)
|
|
break
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ReanimatedSwipeable
|
|
friction={2}
|
|
enableTrackpadTwoFingerGesture
|
|
rightThreshold={40}
|
|
renderRightActions={(prog, drag) => RightAction(prog, drag, member)}>
|
|
<View>
|
|
<Pressable onLongPress={showActions} className='ios:active:bg-linkColor flex-row items-center justify-between rounded-lg'>
|
|
<View className='gap-3 px-4 py-3 flex-row items-center'>
|
|
<UserAvatar
|
|
src={member.user_image ?? ""}
|
|
alt={member.full_name ?? ""}
|
|
availabilityStatus={member.availability_status}
|
|
/>
|
|
<View className='flex-col gap-0.5 justify-center'>
|
|
<View className='flex-row gap-2'>
|
|
<Text className='text-[15px] text-foreground font-medium'>
|
|
{member.full_name?.length > 40
|
|
? `${member.full_name.slice(0, 40)}...`
|
|
: member.full_name}
|
|
{member.name === currentUserInfo?.name ? " (You)" : ""}
|
|
</Text>
|
|
{channelMembers[member.name].is_admin ? <CrownIcon fill="#FFC53D" width={16} height={16} /> : null}
|
|
</View>
|
|
<Text className='text-sm text-muted-foreground'>{member.name}</Text>
|
|
</View>
|
|
</View>
|
|
</Pressable>
|
|
</View>
|
|
</ReanimatedSwipeable>
|
|
)
|
|
}
|
|
|
|
export default ChannelMemberRow |