diff --git a/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx b/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx index 6b65e5d75..4b15a441a 100644 --- a/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx +++ b/frontends/nextjs/src/components/level/level2/ChatTabContent.tsx @@ -1,19 +1,19 @@ -import type { User } from '@/lib/level-types' +import { Box, Typography } from '@mui/material' -import { IRCWebchatDeclarative } from '../../misc/demos/IRCWebchatDeclarative' import { ResultsPane } from '../sections/ResultsPane' -interface ChatTabContentProps { - user: User -} - -export function ChatTabContent({ user }: ChatTabContentProps) { +export function ChatTabContent() { return ( - + + IRC Webchat + + This component is now a Lua package. See packages/irc_webchat/ + + ) } diff --git a/frontends/nextjs/src/components/level/levels/Level2.tsx b/frontends/nextjs/src/components/level/levels/Level2.tsx index d7fb4011e..360d320db 100644 --- a/frontends/nextjs/src/components/level/levels/Level2.tsx +++ b/frontends/nextjs/src/components/level/levels/Level2.tsx @@ -94,7 +94,7 @@ export function Level2({ user, onLogout, onNavigate }: Level2Props) { - + diff --git a/frontends/nextjs/src/components/misc/demos/IRCWebchatDeclarative.tsx b/frontends/nextjs/src/components/misc/demos/IRCWebchatDeclarative.tsx deleted file mode 100644 index e3e08398d..000000000 --- a/frontends/nextjs/src/components/misc/demos/IRCWebchatDeclarative.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file has been refactored into modular lambda-per-file structure. - * - * Import individual functions or use the class wrapper: - * @example - * import { IRCWebchatDeclarative } from './IRCWebchatDeclarative' - * - * @example - * import { IRCWebchatDeclarativeUtils } from './IRCWebchatDeclarative' - * IRCWebchatDeclarativeUtils.IRCWebchatDeclarative(...) - */ - -export * from './IRCWebchatDeclarative' diff --git a/frontends/nextjs/src/components/misc/demos/irc/ChatWindow.tsx b/frontends/nextjs/src/components/misc/demos/irc/ChatWindow.tsx deleted file mode 100644 index f658158e1..000000000 --- a/frontends/nextjs/src/components/misc/demos/irc/ChatWindow.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { Gear, PaperPlaneTilt, SignOut, Users } from '@phosphor-icons/react' - -import { - Badge, - Button, - Card, - CardContent, - CardHeader, - CardTitle, - Input, - ScrollArea, -} from '@/components/ui' - -import type { ChatMessage } from './types' -import { UserList } from './UserList' - -interface ChatWindowProps { - channelName: string - messages: ChatMessage[] - formattedTimes: Record - onlineUsers: string[] - inputMessage: string - onInputChange: (value: string) => void - onSendMessage: () => void - onToggleSettings: () => void - showSettings: boolean - onClose?: () => void - onInputKeyPress?: (event: React.KeyboardEvent) => void -} - -export function ChatWindow({ - channelName, - messages, - formattedTimes, - onlineUsers, - inputMessage, - onInputChange, - onSendMessage, - onToggleSettings, - showSettings, - onClose, - onInputKeyPress, -}: ChatWindowProps) { - const getMessageStyle = (message: ChatMessage) => { - if ( - message.type === 'system' || - message.type === 'join' || - message.type === 'leave' || - message.type === 'command' - ) { - return 'text-muted-foreground italic text-sm' - } - return '' - } - - return ( - - -
- - # - {channelName} - -
- - - {onlineUsers.length} - - - {onClose && ( - - )} -
-
-
- -
- -
- {messages.map(message => ( -
- {message.type === 'message' && ( -
- - {formattedTimes[message.id] || ''} - - - <{message.username}> - - {message.message} -
- )} - - {message.type === 'system' && message.username === 'System' && ( -
- - {formattedTimes[message.id] || ''} - - *** {message.message} -
- )} - - {message.type === 'system' && message.username !== 'System' && ( -
- - {formattedTimes[message.id] || ''} - - - * {message.username} {message.message} - -
- )} - - {(message.type === 'join' || message.type === 'leave') && ( -
- - {formattedTimes[message.id] || ''} - - - --> {message.message} - -
- )} - - {message.type === 'command' && ( -
- - {formattedTimes[message.id] || ''} - - {message.message} -
- )} -
- ))} -
-
- - {showSettings && ( -
-

Online Users

- -
- )} -
- -
-
- onInputChange(event.target.value)} - onKeyPress={onInputKeyPress} - placeholder="Type a message... (/help for commands)" - className="flex-1 font-mono" - /> - -
-

- Press Enter to send. Type /help for commands. -

-
-
-
- ) -} diff --git a/frontends/nextjs/src/components/misc/demos/irc/UserList.tsx b/frontends/nextjs/src/components/misc/demos/irc/UserList.tsx deleted file mode 100644 index 524d53a9c..000000000 --- a/frontends/nextjs/src/components/misc/demos/irc/UserList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -interface UserListProps { - users: string[] -} - -export function UserList({ users }: UserListProps) { - if (users.length === 0) { - return

No users online

- } - - return ( -
- {users.map(username => ( -
-
- {username} -
- ))} -
- ) -} diff --git a/frontends/nextjs/src/components/misc/demos/irc/hooks.ts b/frontends/nextjs/src/components/misc/demos/irc/hooks.ts deleted file mode 100644 index 6b58cf763..000000000 --- a/frontends/nextjs/src/components/misc/demos/irc/hooks.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { useEffect, useState } from 'react' - -import type { ChatMessage } from './types' - -type TimestampFormatter = (timestamp: number) => Promise | string - -export function useChatInput(onSubmit: () => void) { - const [inputMessage, setInputMessage] = useState('') - - const handleKeyPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault() - onSubmit() - } - } - - return { - inputMessage, - setInputMessage, - handleKeyPress, - } -} - -export function useFormattedTimes( - messages: ChatMessage[] | undefined, - formatTime: TimestampFormatter -) { - const [formattedTimes, setFormattedTimes] = useState>({}) - - useEffect(() => { - let isMounted = true - - const formatAllTimes = async () => { - if (!messages) { - setFormattedTimes({}) - return - } - - const entries = await Promise.all( - messages.map(async message => { - const formatted = await formatTime(message.timestamp) - return [message.id, formatted] as const - }) - ) - - if (isMounted) { - setFormattedTimes(Object.fromEntries(entries)) - } - } - - formatAllTimes() - - return () => { - isMounted = false - } - }, [messages, formatTime]) - - return formattedTimes -} diff --git a/frontends/nextjs/src/components/misc/demos/irc/types.ts b/frontends/nextjs/src/components/misc/demos/irc/types.ts deleted file mode 100644 index c46671dc7..000000000 --- a/frontends/nextjs/src/components/misc/demos/irc/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type ChatMessageType = 'message' | 'system' | 'join' | 'leave' | 'command' - -export interface ChatMessage { - id: string - username: string - userId: string - message: string - timestamp: number - type: ChatMessageType -} diff --git a/frontends/nextjs/src/components/organisms/index.ts b/frontends/nextjs/src/components/organisms/index.ts index c6c590af2..40b089ba8 100644 --- a/frontends/nextjs/src/components/organisms/index.ts +++ b/frontends/nextjs/src/components/organisms/index.ts @@ -105,7 +105,6 @@ export { FieldRenderer } from '../FieldRenderer' export { GenericPage } from '../GenericPage' export { GitHubActionsFetcher } from '../GitHubActionsFetcher' export { GodCredentialsSettings } from '../GodCredentialsSettings' -export { IRCWebchatDeclarative } from '../misc/demos/IRCWebchatDeclarative' export { JsonEditor } from '../JsonEditor' export { ContactSection } from '../level1/ContactSection' export { FeaturesSection } from '../level1/FeaturesSection' diff --git a/frontends/nextjs/src/components/rendering/components/RenderNode.tsx b/frontends/nextjs/src/components/rendering/components/RenderNode.tsx index 5a14a3b84..cf3b66176 100644 --- a/frontends/nextjs/src/components/rendering/components/RenderNode.tsx +++ b/frontends/nextjs/src/components/rendering/components/RenderNode.tsx @@ -1,6 +1,5 @@ import type React from 'react' -import { IRCWebchatDeclarative } from '@/components/misc/demos/IRCWebchatDeclarative' import { NotificationSummaryCard } from '@/components/NotificationSummaryCard' import { Alert, @@ -39,21 +38,11 @@ export function RenderNode({ component, renderChildren, user }: RenderNodeProps) const renderer = getDeclarativeRenderer() if (renderer.hasComponentConfig(type)) { - if (type === 'IRCWebchat' && user) { - return ( - - ) - } - return (
- Declarative Component: {type} + Lua Package Component: {type}
- This is a package-defined component + This component is rendered from packages/irc_webchat
) diff --git a/packages/irc_webchat/seed/layout.json b/packages/irc_webchat/seed/layout.json new file mode 100644 index 000000000..d924e5c53 --- /dev/null +++ b/packages/irc_webchat/seed/layout.json @@ -0,0 +1,136 @@ +{ + "id": "irc_webchat_layout", + "type": "Card", + "props": { + "className": "h-[500px] flex flex-col" + }, + "children": [ + { + "id": "header", + "type": "CardHeader", + "props": { + "className": "border-b pb-3" + }, + "children": [ + { + "id": "title_row", + "type": "Flex", + "props": { + "className": "items-center justify-between" + }, + "children": [ + { + "id": "channel_title", + "type": "Typography", + "props": { + "variant": "h6", + "text": "#{channelName}" + } + }, + { + "id": "user_count", + "type": "Badge", + "props": { + "variant": "secondary", + "text": "{onlineUsersCount} online" + } + } + ] + } + ] + }, + { + "id": "messages_area", + "type": "ScrollArea", + "props": { + "className": "flex-1 p-4" + }, + "children": [ + { + "id": "message_list", + "type": "Stack", + "props": { + "spacing": 1, + "dataSource": "messages" + }, + "itemTemplate": { + "id": "message_item", + "type": "Box", + "props": { + "className": "font-mono text-sm" + }, + "children": [ + { + "id": "timestamp", + "type": "Typography", + "props": { + "variant": "caption", + "color": "text.secondary", + "text": "[{formattedTime}]" + } + }, + { + "id": "username", + "type": "Typography", + "props": { + "variant": "body2", + "component": "span", + "fontWeight": "bold", + "text": " <{username}> " + } + }, + { + "id": "message_text", + "type": "Typography", + "props": { + "variant": "body2", + "component": "span", + "text": "{message}" + } + } + ] + } + } + ] + }, + { + "id": "input_area", + "type": "Box", + "props": { + "className": "border-t p-4" + }, + "children": [ + { + "id": "input_row", + "type": "Flex", + "props": { + "gap": 2 + }, + "children": [ + { + "id": "message_input", + "type": "TextField", + "props": { + "fullWidth": true, + "size": "small", + "placeholder": "Type a message... (/help for commands)", + "value": "{inputMessage}", + "onKeyPress": "handleKeyPress", + "onChange": "updateInputMessage" + } + }, + { + "id": "send_button", + "type": "Button", + "props": { + "variant": "contained", + "onClick": "handleSendMessage", + "text": "Send" + } + } + ] + } + ] + } + ] +} diff --git a/packages/irc_webchat/seed/metadata.json b/packages/irc_webchat/seed/metadata.json index 07ba1ab6e..856c8f96a 100644 --- a/packages/irc_webchat/seed/metadata.json +++ b/packages/irc_webchat/seed/metadata.json @@ -9,6 +9,7 @@ "dependencies": [], "exports": { "components": ["IRCWebchat"], + "layouts": ["layout.json"], "luaScripts": [ "send_message", "handle_command",