diff --git a/apps/api/package.json b/apps/api/package.json index cdd1456..2612f9c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,9 +10,9 @@ "test": "ts-node --test test/**/*.test.ts", "start": "node dist/index.js", "dev": "nodemon --delay 2000ms src/index.ts", - "prisma:generate": "prisma generate", - "prisma:migrate": "prisma migrate dev", - "prisma:init": "prisma migrate dev --name init", + "prisma:generate": "dotenv -e ../../.env -- prisma generate", + "prisma:migrate": "dotenv -e ../../.env -- prisma migrate dev", + "prisma:init": "dotenv -e ../../.env -- prisma migrate dev --name init", "build": "tsc" }, "keywords": [], diff --git a/apps/api/prisma/migrations/20250307053052_update_content_type/migration.sql b/apps/api/prisma/migrations/20250307053052_update_content_type/migration.sql new file mode 100644 index 0000000..3751c22 --- /dev/null +++ b/apps/api/prisma/migrations/20250307053052_update_content_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `Message` MODIFY `content` TEXT NOT NULL; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index fd806ca..d7a4c36 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -40,7 +40,7 @@ model Room { model Message { id String @id @default(uuid()) - content String + content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String diff --git a/apps/web/package.json b/apps/web/package.json index 1e25760..3848aba 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,6 +11,7 @@ "dependencies": { "@urql/core": "^5.1.1", "@urql/solid": "^0.1.2", + "emoji-picker-element": "^1.26.1", "graphql-ws": "^6.0.4", "solid-js": "^1.9.5", "zod": "^3.24.2" diff --git a/apps/web/src/App.css b/apps/web/src/App.css index 7a6fa02..a5d63a1 100644 --- a/apps/web/src/App.css +++ b/apps/web/src/App.css @@ -449,3 +449,32 @@ button:disabled { height: 50vh; } } + +/* Emoji Picker Styles */ +.message-input-container { + display: flex; + align-items: center; + flex: 1; +} + +.emoji-toggle-button { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + margin-left: 0.5rem; + padding: 0.25rem; + line-height: 1; +} + +.emoji-picker-container { + position: absolute; + bottom: 100%; + right: 0; + z-index: 10; + margin-bottom: 0.5rem; +} + +.message-form { + position: relative; +} diff --git a/apps/web/src/components/chat-room.tsx b/apps/web/src/components/chat-room.tsx index 454ddad..8250620 100644 --- a/apps/web/src/components/chat-room.tsx +++ b/apps/web/src/components/chat-room.tsx @@ -1,7 +1,10 @@ -import { createSignal, createEffect, For, Show } from 'solid-js'; +import { createSignal, createEffect, For, Show, onMount } from 'solid-js'; import { gql } from '@urql/core'; import { createQuery, createMutation, createSubscription } from '@urql/solid'; import { Message } from '../types'; +import 'emoji-picker-element'; +import { EmojiClickEvent } from 'emoji-picker-element/shared'; +import { Picker } from 'emoji-picker-element'; const ROOM_QUERY = gql` query GetRoom($id: ID!) { @@ -80,7 +83,13 @@ export function ChatRoom(props: ChatRoomProps) { const [messages, setMessages] = createSignal([]); const [confirmLeave, setConfirmLeave] = createSignal(false); const [leaveError, setLeaveError] = createSignal(null); + const [showEmojiPicker, setShowEmojiPicker] = createSignal(false); + const [emojiPickerRef, setPickerRef] = createSignal( + undefined + ); let messagesContainer: HTMLDivElement | undefined; + let messageInput: HTMLInputElement | undefined; + let emojiPickerContainer: HTMLDivElement | undefined; const scrollToBottom = () => { if (messagesContainer) { @@ -198,6 +207,41 @@ export function ChatRoom(props: ChatRoomProps) { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }; + const toggleEmojiPicker = () => { + setShowEmojiPicker(!showEmojiPicker()); + }; + + createEffect(() => { + const handleEmojiSelect = (event: EmojiClickEvent) => { + setMessage((prev) => prev + event.detail.unicode); + messageInput?.focus(); + }; + const ref = emojiPickerRef(); + if (ref) { + ref.addEventListener('emoji-click', handleEmojiSelect); + return () => { + ref.removeEventListener('emoji-click', handleEmojiSelect); + }; + } + }); + + // Close emoji picker when clicking outside + onMount(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + emojiPickerContainer && + !emojiPickerContainer.contains(event.target as Node) + ) { + setShowEmojiPicker(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }); + return (
Loading room...
}> @@ -246,13 +290,30 @@ export function ChatRoom(props: ChatRoomProps) {
- setMessage(e.currentTarget.value)} - placeholder='Type a message...' - /> +
+ setMessage(e.currentTarget.value)} + placeholder='Type a message...' + /> + +
+ + {showEmojiPicker() && ( +
+ {/* @ts-ignore */} + setPickerRef(el)}> +
+ )}
); diff --git a/package-lock.json b/package-lock.json index f855681..93cb991 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "dependencies": { "@urql/core": "^5.1.1", "@urql/solid": "^0.1.2", + "emoji-picker-element": "^1.26.1", "graphql-ws": "^6.0.4", "solid-js": "^1.9.5", "zod": "^3.24.2" @@ -4381,6 +4382,12 @@ "integrity": "sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==", "license": "ISC" }, + "node_modules/emoji-picker-element": { + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.26.1.tgz", + "integrity": "sha512-XgQ9s2JdmworiqLfJC7eGbzQHGv8yb8U9XofjeRAnOMYaeLh0MfwVAz9oG1YE2U2WnzU0Pys1axMjYtPKJ7YSg==", + "license": "Apache-2.0" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",