Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(uiweb): adds commands feature #1282

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 104 additions & 4 deletions packages/uiweb/src/lib/components/chat/MessageInput/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import { ConnectButtonComp } from '../ConnectButton';
import { Modal, ModalHeader } from '../reusables/Modal';
import { ThemeContext } from '../theme/ThemeProvider';

import { PUBLIC_GOOGLE_TOKEN, device } from '../../../config';
import { FRAMES_REGISTRY_URL, PUBLIC_GOOGLE_TOKEN, device } from '../../../config';
import usePushUser from '../../../hooks/usePushUser';
import { MODAL_BACKGROUND_TYPE, MODAL_POSITION_TYPE, type FileMessageContent } from '../../../types';
import { GIFType, Group, IChatTheme, MessageInputProps } from '../exportedTypes';
import { checkIfAccessVerifiedGroup } from '../helpers';
import { InfoContainer } from '../reusables';
import { IChatInfoResponse } from '../types';

import { IChatInfoResponse, FrameCommand } from '../types';
import { extractDynamicArgs } from '../../../utilities/getFrameUrlDynamicParams';

/**
* @interface IThemeProps
Expand Down Expand Up @@ -85,7 +87,8 @@ export const MessageInput: React.FC<MessageInputProps> = ({
const [isRules, setIsRules] = useState<boolean>(false);
const [isMember, setIsMember] = useState<boolean>(false);
const [formattedChatId, setFormattedChatId] = useState<string>('');

const [allFrameCommands, setAllFrameCommands] = useState<FrameCommand[]>([]); // all available frame commands
const [filteredFrameCommands, setFilteredFrameCommands] = useState<FrameCommand[]>([]); // filtered frame commands based on typed command
const { getGroupByIDnew } = useGetGroupByIDnew();
const [groupInfo, setGroupInfo] = useState<Group | null>(null);

Expand Down Expand Up @@ -119,6 +122,22 @@ export const MessageInput: React.FC<MessageInputProps> = ({

const onChangeTypedMessage = (val: string) => {
setTypedMessage(val);
if (val === '') setFilteredFrameCommands([]);
// Check if the message contains a slash and process accordingly
if (val.includes('/')) {
const parts = val.split(' ');

const commandIndex = parts.findIndex((part) => part.startsWith('/'));

if (commandIndex !== -1) {
// Filter commands that start with the typed command part
const matchingCommands = allFrameCommands.filter((command) => command.command.startsWith(parts[commandIndex]));

if (matchingCommands.length > 0) {
setFilteredFrameCommands(matchingCommands);
}
}
}
};
useClickAway(modalRef, () => {
setShowEmojis(false);
Expand Down Expand Up @@ -249,6 +268,20 @@ export const MessageInput: React.FC<MessageInputProps> = ({
}
}, [chatAcceptStream]);

// fetch all available frame commands on mount
useEffect(() => {
const fetchAllFrameCommands = async () => {
console.log('fetching all frame commands');
const response = await fetch(FRAMES_REGISTRY_URL);
const data = await response.json();
if (data) {
console.log('data', data);
setAllFrameCommands(data);
}
};
fetchAllFrameCommands();
}, []);

const transformGroupDetails = (item: any): void => {
if (groupInfo?.chatId === item?.chatId) {
const updatedGroupInfo = groupInfo;
Expand Down Expand Up @@ -387,11 +420,32 @@ export const MessageInput: React.FC<MessageInputProps> = ({

const sendPushMessage = async (content: string, type: string) => {
try {
if (content.includes('/')) {
const parts = content.split(' ');
const commandIndex = parts.findIndex((part) => part.startsWith('/'));
const frameCommand = parts[commandIndex];
const matchingCommand = allFrameCommands.find((command) => command.command === frameCommand);
if (matchingCommand) {
let url = matchingCommand.url;
const dynamicArgs = extractDynamicArgs(url);
const dynamicArgsValues = parts.slice(commandIndex + 1, parts.length);

dynamicArgs.forEach((arg, index) => {
url = url.replace(`\${${arg}}`, dynamicArgsValues[index]);
});
const textBeforeCommand = parts.slice(0, commandIndex).join(' ');
content = `${textBeforeCommand} ${url} ${dynamicArgsValues.slice(dynamicArgs.length).join(' ')}`;
}

setFilteredFrameCommands([]);
}

const sendMessageResponse = await sendMessage({
message: content,
chatId: formattedChatId,
messageType: type as any,
});

if (sendMessageResponse && typeof sendMessageResponse === 'string' && sendMessageResponse.includes('403')) {
setAccessControl(chatId, true);
setVerified(false);
Expand All @@ -401,7 +455,6 @@ export const MessageInput: React.FC<MessageInputProps> = ({
console.log(error);
}
};

const sendTextMsg = async () => {
if (typedMessage.trim() !== '') {
await sendPushMessage(typedMessage as string, 'Text');
Expand Down Expand Up @@ -433,7 +486,53 @@ export const MessageInput: React.FC<MessageInputProps> = ({
justifyContent="space-between"
alignItems="center"
className={chatInfo?.list === 'REQUESTS' ? 'hide' : ''}
position="relative"
>
{filteredFrameCommands && filteredFrameCommands.length > 0 && (
<Section
background="white"
flexDirection="column"
gap="10px"
position="absolute"
bottom="55px"
width="100%"
padding="8px 0"
borderRadius="13px"
maxHeight="200px"
overflow="auto"
>
{filteredFrameCommands.map((command, index) => (
<Section
key={index}
gap="4px"
flexDirection="column"
padding="8px"
alignItems="flex-start"
justifyContent="flex-start"
width="94%"
margin="0 auto"
border="1px solid #eadfdf"
borderRadius="13px"
>
<Span
fontWeight="600"
fontSize="18px"
>
{command.command}
{`${
extractDynamicArgs(command.url).length > 0 ? ` [${extractDynamicArgs(command.url).join('] [')}]` : ``
}`}
</Span>
<Span
fontSize="14px"
fontWeight="400"
>
{command.description ?? 'This Frame does something '}
</Span>
</Section>
))}
</Section>
)}
<TypebarSection
width="100%"
overflow="hidden"
Expand Down Expand Up @@ -582,6 +681,7 @@ export const MessageInput: React.FC<MessageInputProps> = ({
/>
</Section>
)}

<MultiLineInput
disabled={loading ? true : false}
theme={theme}
Expand Down
15 changes: 11 additions & 4 deletions packages/uiweb/src/lib/components/chat/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const GROUP_ROLES = {
MEMBER: 'MEMBER',
} as const;

export type GroupRolesKeys = typeof GROUP_ROLES[keyof typeof GROUP_ROLES];
export type GroupRolesKeys = (typeof GROUP_ROLES)[keyof typeof GROUP_ROLES];
export interface ChatMemberCounts {
overallCount: number;
adminsCount: number;
Expand Down Expand Up @@ -37,7 +37,7 @@ export const TYPE = {
GUILD: 'GUILD',
} as const;

export type TypeKeys = typeof TYPE[keyof typeof TYPE];
export type TypeKeys = (typeof TYPE)[keyof typeof TYPE];

export const CATEGORY = {
ERC20: 'ERC20',
Expand All @@ -51,7 +51,7 @@ export const UNIT = {
ERC20: 'TOKEN',
ERC721: 'NFT',
} as const;
export type UnitKeys = typeof UNIT[keyof typeof UNIT];
export type UnitKeys = (typeof UNIT)[keyof typeof UNIT];
export const SUBCATEGORY = {
HOLDER: 'holder',
OWENER: 'owner',
Expand All @@ -65,7 +65,7 @@ export type ReadonlyInputType = {
title: string;
};
export type InputType = DropdownValueType[] | ReadonlyInputType;
export type SubCategoryKeys = typeof CATEGORY[keyof typeof CATEGORY];
export type SubCategoryKeys = (typeof CATEGORY)[keyof typeof CATEGORY];

export type DropdownCategoryValuesType = {
[key in TypeKeys]: InputType;
Expand Down Expand Up @@ -111,4 +111,11 @@ export interface IChatInfoResponse {
participants?: Array<string>;
recipient?: string;
}

export interface FrameCommand {
owner: string;
command: string;
url: string;
description?: string;
}
export * from './tokenGatedGroupCreationType';
13 changes: 9 additions & 4 deletions packages/uiweb/src/lib/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export const allowedNetworks = {
123, // for fuse testnet
80085, // for berachain testnet
2442, // polygon zkevm

111557560 // cyber connect testnet

],
staging: [
// 42, //for kovan
Expand All @@ -111,7 +113,9 @@ export const allowedNetworks = {
123, // for fuse testnet
80085, // for berachain testnet
2442, // polygon zkevm

111557560 // cyber connect testnet

],
local: [
11155111, // for eth sepolia
Expand All @@ -122,7 +126,9 @@ export const allowedNetworks = {
123, // for fuse testnet
80085, // for berachain testnet
2442, // polygon zkevm

111557560 // cyber connect testnet

],
};

Expand Down Expand Up @@ -152,8 +158,7 @@ export const FILE_ICON = (extension: string) =>

// Livekit Server URLs
export const LIVEKIT_SERVER_URL = 'https://spacev2-demo-17wvllxz.livekit.cloud';
export const LIVEKIT_SERVER_WEBSOCKET_URL =
'wss://spacev2-demo-17wvllxz.livekit.cloud';
export const LIVEKIT_TOKEN_GENERATOR_SERVER_URL =
'https://ms-lk-server.onrender.com';
export const LIVEKIT_SERVER_WEBSOCKET_URL = 'wss://spacev2-demo-17wvllxz.livekit.cloud';
export const LIVEKIT_TOKEN_GENERATOR_SERVER_URL = 'https://ms-lk-server.onrender.com';
export const GUEST_MODE_ACCOUNT = '0x0000000000000000000000000000000000000001';
export const FRAMES_REGISTRY_URL = 'https://frames.push.org/api/register-frame';
11 changes: 11 additions & 0 deletions packages/uiweb/src/lib/utilities/getFrameUrlDynamicParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function extractDynamicArgs(str: string) {
const regex = /\$\{([^}]+)\}/g;
const matches = [];
let match;

while ((match = regex.exec(str)) !== null) {
matches.push(match[1]);
}

return matches;
}
Loading