Browse Source

fix: easy linting errors & auto formatting

Remove unused
Add linting ignore on true parameter
Autoformatting
pull/603/head
Jeremy Gallant 1 year ago
parent
commit
d8130f8a4e
  1. 14
      src/components/PageComponents/Config/Security/Security.tsx
  2. 39
      src/components/PageComponents/Connect/HTTP.tsx
  3. 2
      src/components/ThemeSwitcher.tsx
  4. 91
      src/core/stores/messageStore/index.ts
  5. 17
      src/core/subscriptions.ts
  6. 246
      src/pages/Messages.tsx

14
src/components/PageComponents/Config/Security/Security.tsx

@ -1,16 +1,12 @@
import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx";
import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import {
getX25519PrivateKey,
getX25519PublicKey,
} from "@core/utils/x25519.ts";
import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts";
import type { SecurityValidation } from "@app/validation/config/security.ts";
import { create } from "@bufbuild/protobuf";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react";
import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
@ -58,7 +54,8 @@ export const Security = () => {
if (input.length % 4 !== 0) {
addError(
fieldName,
`${fieldName === "privateKey" ? "Private" : "Admin"
`${
fieldName === "privateKey" ? "Private" : "Admin"
} Key is required to be a 256 bit pre-shared key (PSK)`,
);
return;
@ -73,7 +70,8 @@ export const Security = () => {
console.error(e);
addError(
fieldName,
`Invalid ${fieldName === "privateKey" ? "Private" : "Admin"
`Invalid ${
fieldName === "privateKey" ? "Private" : "Admin"
} Key format`,
);
}
@ -242,7 +240,7 @@ export const Security = () => {
? getErrorMessage("adminKey")
: "",
inputChange: adminKeyInputChangeEvent,
selectChange: () => { },
selectChange: () => {},
bits: [{ text: "256 bit", value: "32", key: "bit256" }],
devicePSKBitCount: state.privateKeyBitCount,
hide: !state.adminKeyVisible,

39
src/components/PageComponents/Connect/HTTP.tsx

@ -11,7 +11,7 @@ import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http";
import { useState } from "react";
import { useForm, useController } from "react-hook-form";
import { useController, useForm } from "react-hook-form";
import { AlertTriangle } from "lucide-react";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
@ -20,7 +20,10 @@ interface FormData {
tls: boolean;
}
export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgress }: TabElementProps) => {
export const HTTP = (
{ closeDialog, setConnectionInProgress, connectionInProgress }:
TabElementProps,
) => {
const isURLHTTPS = location.protocol === "https:";
const { addDevice } = useDeviceStore();
@ -30,8 +33,8 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
const { control, handleSubmit, register } = useForm<FormData>({
defaultValues: {
ip: ["client.meshtastic.org", "localhost"].includes(
globalThis.location.hostname,
)
globalThis.location.hostname,
)
? "meshtastic.local"
: globalThis.location.host,
tls: isURLHTTPS ? true : false,
@ -42,7 +45,9 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
field: { value: tlsValue, onChange: setTLS },
} = useController({ name: "tls", control });
const [connectionError, setConnectionError] = useState<{ host: string; secure: boolean } | null>(null);
const [connectionError, setConnectionError] = useState<
{ host: string; secure: boolean } | null
>(null);
const onSubmit = handleSubmit(async (data) => {
setConnectionInProgress(true);
@ -91,21 +96,31 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
{connectionError && (
<div className="mt-2 mb-2 p-3 rounded-md bg-amber-100 border border-amber-300 dark:bg-amber-100 dark:border-amber-300">
<div className="flex gap-2 items-start">
<AlertTriangle className="shrink-0 mt-0.5 text-amber-600 dark:text-amber-600" size={20} />
<AlertTriangle
className="shrink-0 mt-0.5 text-amber-600 dark:text-amber-600"
size={20}
/>
<div>
<p className="text-sm font-medium text-amber-800 dark:text-amber-800">
Connection Failed
</p>
<p className="text-xs mt-1 text-amber-700 dark:text-amber-700">
Could not connect to the device. {connectionError.secure && "If using HTTPS, you may need to accept a self-signed certificate first. "}
Could not connect to the device. {connectionError.secure &&
"If using HTTPS, you may need to accept a self-signed certificate first. "}
Please open{" "}
<Link
href={`${connectionError.secure ? "https" : "http"}://${connectionError.host}`}
href={`${
connectionError.secure ? "https" : "http"
}://${connectionError.host}`}
className="underline font-medium text-amber-800 dark:text-amber-800"
>
{`${connectionError.secure ? "https" : "http"}://${connectionError.host}`}
{`${
connectionError.secure ? "https" : "http"
}://${connectionError.host}`}
</Link>{" "}
in a new tab{connectionError.secure ? ", accept any TLS warnings if prompted, then try again" : ""}.{" "}
in a new tab{connectionError.secure
? ", accept any TLS warnings if prompted, then try again"
: ""}.{" "}
<Link
href="https://meshtastic.org/docs/software/web-client/#http"
className="underline font-medium text-amber-800 dark:text-amber-800"
@ -120,10 +135,10 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
</div>
<Button
type="submit"
variant={"default"}
variant="default"
>
<span>{connectionInProgress ? "Connecting..." : "Connect"}</span>
</Button>
</form>
);
};
};

2
src/components/ThemeSwitcher.tsx

@ -9,7 +9,7 @@ export default function ThemeSwitcher({
}: {
className?: string;
}) {
const { theme, preference, setPreference } = useTheme();
const { preference, setPreference } = useTheme();
const themeIcons = {
light: <Sun className="size-6" />,

91
src/core/stores/messageStore/index.ts

@ -1,9 +1,19 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { produce } from 'immer';
import { Types } from '@meshtastic/core';
import { storageWithMapSupport } from "../storage/indexDB.ts";
import { ChannelId, ClearMessageParams, ConversationId, GetMessagesParams, Message, MessageId, MessageLogMap, NodeNum, SetMessageStateParams } from "@core/stores/messageStore/types.ts";
import { create } from "zustand";
// import { persist } from 'zustand/middleware';
import { produce } from "immer";
import { Types } from "@meshtastic/core";
// import { storageWithMapSupport } from "../storage/indexDB.ts";
import {
ChannelId,
ClearMessageParams,
ConversationId,
GetMessagesParams,
Message,
MessageId,
MessageLogMap,
NodeNum,
SetMessageStateParams,
} from "@core/stores/messageStore/types.ts";
export enum MessageState {
Ack = "ack",
@ -16,8 +26,11 @@ export enum MessageType {
Broadcast = "broadcast",
}
export function getConversationId(node1: NodeNum, node2: NodeNum): ConversationId {
return [node1, node2].sort((a, b) => a - b).join(':');
export function getConversationId(
node1: NodeNum,
node2: NodeNum,
): ConversationId {
return [node1, node2].sort((a, b) => a - b).join(":");
}
export interface MessageStore {
@ -25,9 +38,9 @@ export interface MessageStore {
direct: Map<ConversationId, MessageLogMap>;
broadcast: Map<ChannelId, MessageLogMap>;
};
};
}
export interface MessageStore {
messages: MessageStore['messages'];
messages: MessageStore["messages"];
draft: Map<Types.Destination, string>;
nodeNum: number; // This device's node number
activeChat: number; // Represents otherNodeNum for Direct, or channel for Broadcast
@ -47,7 +60,7 @@ export interface MessageStore {
clearDraft: (key: Types.Destination) => void;
}
const CURRENT_STORE_VERSION = 0;
// const CURRENT_STORE_VERSION = 0;
export const useMessageStore = create<MessageStore>()(
// persist(
@ -82,17 +95,29 @@ export const useMessageStore = create<MessageStore>()(
if (message.type === MessageType.Direct) {
const conversationId = getConversationId(message.from, message.to);
if (!state.messages.direct.has(conversationId)) {
state.messages.direct.set(conversationId, new Map<MessageId, Message>());
state.messages.direct.set(
conversationId,
new Map<MessageId, Message>(),
);
}
state.messages.direct.get(conversationId)!.set(message.messageId, message);
state.messages.direct.get(conversationId)!.set(
message.messageId,
message,
);
} else if (message.type === MessageType.Broadcast) {
const channelId = message.channel as ChannelId;
if (!state.messages.broadcast.has(channelId)) {
state.messages.broadcast.set(channelId, new Map<MessageId, Message>());
state.messages.broadcast.set(
channelId,
new Map<MessageId, Message>(),
);
}
state.messages.broadcast.get(channelId)!.set(message.messageId, message);
state.messages.broadcast.get(channelId)!.set(
message.messageId,
message,
);
}
})
}),
);
},
@ -103,7 +128,10 @@ export const useMessageStore = create<MessageStore>()(
let targetMessage: Message | undefined;
if (params.type === MessageType.Direct) {
const conversationId = getConversationId(params.nodeA, params.nodeB);
const conversationId = getConversationId(
params.nodeA,
params.nodeB,
);
messageLog = state.messages.direct.get(conversationId);
if (messageLog) {
targetMessage = messageLog.get(params.messageId);
@ -118,9 +146,13 @@ export const useMessageStore = create<MessageStore>()(
if (targetMessage) {
targetMessage.state = params.newState ?? MessageState.Ack;
} else {
console.warn(`Message or conversation/channel not found for state update. Params: ${JSON.stringify(params)}`);
console.warn(
`Message or conversation/channel not found for state update. Params: ${
JSON.stringify(params)
}`,
);
}
})
}),
);
},
getMessages: (params: GetMessagesParams): Message[] => {
@ -130,7 +162,6 @@ export const useMessageStore = create<MessageStore>()(
if (params.type === MessageType.Direct) {
const conversationId = getConversationId(params.nodeA, params.nodeB);
messageMap = state.messages.direct.get(conversationId);
} else {
messageMap = state.messages.broadcast.get(params.channelId);
}
@ -165,23 +196,29 @@ export const useMessageStore = create<MessageStore>()(
const deleted = messageLog.delete(params.messageId);
if (deleted) {
console.log(`Deleted message ${params.messageId} from ${params.type} message ${parentKey}`);
console.log(
`Deleted message ${params.messageId} from ${params.type} message ${parentKey}`,
);
// Clean up empty MessageLogMap and its entry in the parent map
if (messageLog.size === 0) {
parentMap.delete(parentKey);
console.log(`Cleaned up empty message entry for ${parentKey}`);
}
} else {
console.warn(`Message ${params.messageId} not found in ${params.type} chat ${parentKey} for deletion.`);
console.warn(
`Message ${params.messageId} not found in ${params.type} chat ${parentKey} for deletion.`,
);
}
} else {
console.warn(`Message entry ${parentKey} not found for message deletion.`);
console.warn(
`Message entry ${parentKey} not found for message deletion.`,
);
}
})
}),
);
},
getDraft: (key) => {
return get().draft.get(key) ?? '';
return get().draft.get(key) ?? "";
},
setDraft: (key, message) => {
set(produce((state: MessageStore) => {
@ -198,7 +235,7 @@ export const useMessageStore = create<MessageStore>()(
state.messages.direct = new Map<ConversationId, MessageLogMap>();
state.messages.broadcast = new Map<ChannelId, MessageLogMap>();
}));
}
},
}),
// {
// name: 'meshtastic-message-store',
@ -209,4 +246,4 @@ export const useMessageStore = create<MessageStore>()(
// nodeNum: state.nodeNum,
// }),
// })
)
);

17
src/core/subscriptions.ts

@ -1,13 +1,13 @@
import type { Device } from "@core/stores/deviceStore.ts";
import { MeshDevice, Protobuf } from "@meshtastic/core";
import { MessageState, MessageType, type MessageStore } from "./stores/messageStore/index.ts";
import { type MessageStore, MessageType } from "./stores/messageStore/index.ts";
import PacketToMessageDTO from "@core/dto/PacketToMessageDTO.ts";
import NodeInfoFactory from "@core/dto/NodeNumToNodeInfoDTO.ts";
export const subscribeAll = (
device: Device,
connection: MeshDevice,
messageStore: MessageStore
messageStore: MessageStore,
) => {
let myNodeNum = 0;
@ -79,7 +79,6 @@ export const subscribeAll = (
device.setModuleConfig(moduleConfig);
});
connection.events.onMessagePacket.subscribe((messagePacket) => {
// incoming and outgoing messages are handled by this event listener
console.log("Message Packet", messagePacket);
@ -117,7 +116,6 @@ export const subscribeAll = (
});
});
connection.events.onRoutingPacket.subscribe((routingPacket) => {
if (routingPacket.data.variant.case === "errorReason") {
switch (routingPacket.data.variant.value) {
@ -126,19 +124,24 @@ export const subscribeAll = (
break;
case Protobuf.Mesh.Routing_Error.NO_CHANNEL:
console.error(`Routing Error: ${routingPacket.data.variant.value}`);
device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]);
device.setNodeError(
routingPacket.from,
Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value],
);
device.setDialogOpen("refreshKeys", true);
break;
case Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY:
console.error(`Routing Error: ${routingPacket.data.variant.value}`);
device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]);
device.setNodeError(
routingPacket.from,
Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value],
);
device.setDialogOpen("refreshKeys", true);
break;
default: {
break;
}
}
}
});
};

246
src/pages/Messages.tsx

@ -12,34 +12,55 @@ import { HashIcon, LockIcon, LockOpenIcon } from "lucide-react";
import { useCallback, useDeferredValue, useMemo, useState } from "react";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import { cn } from "@core/utils/cn.ts";
import { MessageState, MessageType, useMessageStore } from "@core/stores/messageStore/index.ts";
import {
MessageState,
MessageType,
useMessageStore,
} from "@core/stores/messageStore/index.ts";
import { useSidebar } from "@core/stores/sidebarStore.tsx";
import { Input } from "@components/UI/Input.tsx";
type NodeInfoWithUnread = Protobuf.Mesh.NodeInfo & { unreadCount: number };
export const MessagesPage = () => {
const { channels, getNodes, getNode, hasNodeError, unreadCounts, resetUnread, connection } = useDevice();
const { getMyNodeNum, getMessages, setActiveChat, chatType, activeChat, setChatType, setMessageState, } = useMessageStore()
const {
channels,
getNodes,
getNode,
hasNodeError,
unreadCounts,
resetUnread,
connection,
} = useDevice();
const {
getMyNodeNum,
getMessages,
setActiveChat,
chatType,
activeChat,
setChatType,
setMessageState,
} = useMessageStore();
const { toast } = useToast();
const { isCollapsed } = useSidebar()
const { isCollapsed } = useSidebar();
const [searchTerm, setSearchTerm] = useState<string>("");
const deferredSearch = useDeferredValue(searchTerm);
const filteredNodes = (): NodeInfoWithUnread[] => {
const lowerCaseSearchTerm = deferredSearch.toLowerCase();
return getNodes(node => {
const longName = node.user?.longName?.toLowerCase() ?? '';
const shortName = node.user?.shortName?.toLowerCase() ?? '';
return longName.includes(lowerCaseSearchTerm) || shortName.includes(lowerCaseSearchTerm)
return getNodes((node) => {
const longName = node.user?.longName?.toLowerCase() ?? "";
const shortName = node.user?.shortName?.toLowerCase() ?? "";
return longName.includes(lowerCaseSearchTerm) ||
shortName.includes(lowerCaseSearchTerm);
})
.map((node) => ({
...node,
unreadCount: unreadCounts.get(node.num) ?? 0,
}))
.sort((a, b) => b.unreadCount - a.unreadCount);
}
};
const allChannels = Array.from(channels.values());
const filteredChannels = allChannels.filter(
@ -55,19 +76,39 @@ export const MessagesPage = () => {
const isDirect = chatType === MessageType.Direct;
const toValue = isDirect ? activeChat : MessageType.Broadcast;
const channelValue = isDirect ? Types.ChannelNumber.Primary : activeChat ?? 0;
const channelValue = isDirect
? Types.ChannelNumber.Primary
: activeChat ?? 0;
console.log(`Sending message: "${message}" to: ${toValue}, channel: ${channelValue}, type: ${chatType}`);
console.log(
`Sending message: "${message}" to: ${toValue}, channel: ${channelValue}, type: ${chatType}`,
);
let messageId: number | undefined;
try {
messageId = await connection?.sendText(message, toValue, true, channelValue);
messageId = await connection?.sendText(
message,
toValue,
true,
channelValue,
);
if (messageId !== undefined) {
if (chatType === MessageType.Broadcast) {
setMessageState({ type: chatType, channelId: channelValue, messageId, newState: MessageState.Ack });
setMessageState({
type: chatType,
channelId: channelValue,
messageId,
newState: MessageState.Ack,
});
} else {
setMessageState({ type: chatType, nodeA: getMyNodeNum(), nodeB: activeChat, messageId, newState: MessageState.Ack });
setMessageState({
type: chatType,
nodeA: getMyNodeNum(),
nodeB: activeChat,
messageId,
newState: MessageState.Ack,
});
}
} else {
console.warn("sendText completed but messageId is undefined");
@ -78,10 +119,21 @@ export const MessagesPage = () => {
// Note: messageId might be undefined here if the error occurred before it was assigned
if (chatType === MessageType.Broadcast) {
const failedId = messageId ?? `failed-${Date.now()}`;
setMessageState({ type: chatType, channelId: channelValue, messageId: failedId, newState: MessageState.Failed });
setMessageState({
type: chatType,
channelId: channelValue,
messageId: failedId,
newState: MessageState.Failed,
});
} else { // MessageType.Direct
const failedId = messageId ?? `failed-${Date.now()}`;
setMessageState({ type: chatType, nodeA: getMyNodeNum(), nodeB: activeChat, messageId: failedId, newState: MessageState.Failed });
setMessageState({
type: chatType,
nodeA: getMyNodeNum(),
nodeB: activeChat,
messageId: failedId,
newState: MessageState.Failed,
});
}
}
}, [activeChat, chatType, connection, getMyNodeNum, setMessageState]);
@ -100,7 +152,11 @@ export const MessagesPage = () => {
case MessageType.Direct:
return (
<ChannelChat
messages={getMessages({ type: MessageType.Direct, nodeA: getMyNodeNum(), nodeB: activeChat })}
messages={getMessages({
type: MessageType.Direct,
nodeA: getMyNodeNum(),
nodeB: activeChat,
})}
/>
);
default:
@ -110,7 +166,7 @@ export const MessagesPage = () => {
</div>
);
}
}
};
const leftSidebar = useMemo(() => (
<Sidebar>
@ -119,72 +175,108 @@ export const MessagesPage = () => {
<SidebarButton
key={channel.index}
count={unreadCounts.get(channel.index)}
label={channel.settings?.name || (channel.index === 0 ? "Primary" : `Ch ${channel.index}`)}
active={activeChat === channel.index && chatType === MessageType.Broadcast}
label={channel.settings?.name ||
(channel.index === 0 ? "Primary" : `Ch ${channel.index}`)}
active={activeChat === channel.index &&
chatType === MessageType.Broadcast}
onClick={() => {
setChatType(MessageType.Broadcast);
setActiveChat(channel.index);
resetUnread(channel.index);
}}
>
<HashIcon size={16} className={cn(isCollapsed ? "mr-0 mt-2" : "mr-2")} />
<HashIcon
size={16}
className={cn(isCollapsed ? "mr-0 mt-2" : "mr-2")}
/>
</SidebarButton>
))}
</SidebarSection>
</Sidebar>
), [filteredChannels, unreadCounts, activeChat, chatType, isCollapsed, setActiveChat, setChatType, resetUnread]);
const rightSidebar = useMemo(() => (
<SidebarSection label="" className="px-0 flex flex-col h-full overflow-y-auto">
<label className="p-2 block">
<Input
type="text"
placeholder="Search nodes..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
showClearButton={!!searchTerm}
/>
</label>
<div className={cn(
"flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ",
)}>
{filteredNodes()?.map((node) => (
<SidebarButton
key={node.num}
preventCollapse={true}
label={node.user?.longName ?? `UNK`}
count={node.unreadCount > 0 ? node.unreadCount : undefined}
active={activeChat === node.num && chatType === MessageType.Direct}
onClick={() => {
setChatType(MessageType.Direct);
setActiveChat(node.num);
resetUnread(node.num);
}}>
<Avatar
text={node.user?.shortName ?? "UNK"}
className={cn(hasNodeError(node.num) && "text-red-500")}
showError={hasNodeError(node.num)}
size="sm"
/>
</SidebarButton>
))}
</div>
</SidebarSection>
), [filteredNodes, searchTerm, activeChat, chatType, setActiveChat, setChatType, resetUnread, hasNodeError]);
), [
filteredChannels,
unreadCounts,
activeChat,
chatType,
isCollapsed,
setActiveChat,
setChatType,
resetUnread,
]);
const rightSidebar = useMemo(
() => (
<SidebarSection
label=""
className="px-0 flex flex-col h-full overflow-y-auto"
>
<label className="p-2 block">
<Input
type="text"
placeholder="Search nodes..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
showClearButton={!!searchTerm}
/>
</label>
<div
className={cn(
"flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ",
)}
>
{filteredNodes()?.map((node) => (
<SidebarButton
key={node.num}
// Default value for preventCollapse is false, hence ignore linting error:
// deno-lint-ignore jsx-boolean-value
preventCollapse={true}
label={node.user?.longName ?? `UNK`}
count={node.unreadCount > 0 ? node.unreadCount : undefined}
active={activeChat === node.num &&
chatType === MessageType.Direct}
onClick={() => {
setChatType(MessageType.Direct);
setActiveChat(node.num);
resetUnread(node.num);
}}
>
<Avatar
text={node.user?.shortName ?? "UNK"}
className={cn(hasNodeError(node.num) && "text-red-500")}
showError={hasNodeError(node.num)}
size="sm"
/>
</SidebarButton>
))}
</div>
</SidebarSection>
),
[
filteredNodes,
searchTerm,
activeChat,
chatType,
setActiveChat,
setChatType,
resetUnread,
hasNodeError,
],
);
return (
<PageLayout
label={`Messages: ${isBroadcast && currentChannel
? getChannelName(currentChannel)
: isDirect && otherNode
label={`Messages: ${
isBroadcast && currentChannel
? getChannelName(currentChannel)
: isDirect && otherNode
? (otherNode.user?.longName ?? "Unknown")
: "Select a Chat"
}`}
}`}
rightBar={rightSidebar}
leftBar={leftSidebar}
actions={isDirect && otherNode
? [
{
key: 'encryption',
key: "encryption",
icon: otherNode.user?.publicKey?.length ? LockIcon : LockOpenIcon,
iconClasses: otherNode.user?.publicKey?.length
? "text-green-600"
@ -204,19 +296,23 @@ export const MessagesPage = () => {
{renderChatContent()}
<div className="flex-none dark:bg-slate-900 p-2">
{(isBroadcast || isDirect) ? (
<MessageInput
to={isDirect ? activeChat : MessageType.Broadcast}
onSend={sendText}
maxBytes={200}
/>
) : (
<div className="p-4 text-center text-slate-400 italic">Select a chat to send a message.</div>
)}
{(isBroadcast || isDirect)
? (
<MessageInput
to={isDirect ? activeChat : MessageType.Broadcast}
onSend={sendText}
maxBytes={200}
/>
)
: (
<div className="p-4 text-center text-slate-400 italic">
Select a chat to send a message.
</div>
)}
</div>
</div>
</PageLayout>
);
};
export default MessagesPage;
export default MessagesPage;

Loading…
Cancel
Save