changed from input to textbox to keep text formatting, added daisyUI, refactored dropdown menu

This commit is contained in:
slawk0
2024-11-15 22:44:02 +01:00
parent 5ba57cefac
commit 88307104a2
6 changed files with 254 additions and 75 deletions

View File

@@ -1,12 +1,15 @@
import { sendMessage } from '../../socket/socket.tsx'; import { sendMessage } from '../../socket/socket.tsx';
import { useForm, SubmitHandler } from 'react-hook-form'; import { useForm, SubmitHandler } from 'react-hook-form';
type Input = { type Input = {
message: string; message: string;
}; };
type MessaFormProps = { type MessaFormProps = {
contact: string; contact: string;
}; };
function messageForm({ contact }: MessaFormProps) {
function MessageForm({ contact }: MessaFormProps) {
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -17,46 +20,59 @@ function messageForm({ contact }: MessaFormProps) {
// Sending message // Sending message
const submitMessage: SubmitHandler<Input> = (data) => { const submitMessage: SubmitHandler<Input> = (data) => {
console.log(contact); console.log(contact);
// block sending empty message
// Block sending empty message
if (!data.message) { if (!data.message) {
return; return;
} }
// for (let i = 0; i <= 200; i++) {
// let ii = i.toString();
// sendMessage(ii, contact);
// }
sendMessage(data.message, contact);
sendMessage(data.message, contact);
reset({ message: '' }); reset({ message: '' });
}; };
const adjustSize = (event) => {
const textarea = event.target;
// Adjust height
textarea.style.height = 'auto';
const maxHeight = 500;
if (textarea.scrollHeight > maxHeight) {
textarea.style.height = `${maxHeight}px`;
textarea.style.overflowY = 'auto';
} else {
textarea.style.height = `${textarea.scrollHeight}px`;
textarea.style.overflowY = 'hidden';
}
};
return ( return (
<>
<form onSubmit={handleSubmit(submitMessage)}> <form onSubmit={handleSubmit(submitMessage)}>
<div className="flex"> <div className="flex items-end">
<input <textarea
className=" bg-green-50 text-black rounded-md w-full ml-1 mr-1 size-10" className="resize-none overflow-y-hidden bg-green-50 text-black rounded-md w-full ml-1 mr-1 size-10"
type="text"
autoFocus={!!contact} autoFocus={!!contact}
disabled={!contact} disabled={!contact}
placeholder="Enter message" placeholder="Enter message"
onInput={adjustSize}
rows={1}
{...register('message', { {...register('message', {
maxLength: 500, maxLength: 2000,
minLength: 1, minLength: 1,
})} })}
/> />
{errors?.message?.type === 'maxLength' && ( {errors?.message?.type === 'maxLength' && (
<p>Maximum length of the message is 500</p> <p>Maximum length of the message is 2000</p>
)} )}
<button <button
className="text-black bg-green-500 hover:bg-green-400 font-bold py-2 px-4 rounded mr-1 w-24 " className="text-black bg-green-500 hover:bg-green-400 font-bold py-2 px-4 rounded mr-1 w-24"
type="submit" type="submit"
> >
Send Send
</button> </button>
</div> </div>
</form> </form>
</>
); );
} }
export default messageForm;
export default MessageForm;

View File

@@ -126,14 +126,17 @@ function MessagesArea({
}, [messages]); }, [messages]);
const messageList = messages.map((msg: ChatMessages) => ( const messageList = messages.map((msg: ChatMessages) => (
<li className="ml-2 rounded p-1 hover:bg-gray-800" key={msg.message_id}> <li
className="whitespace-pre-wrap ml-2 rounded p-1 hover:bg-gray-800"
key={msg.message_id}
>
{msg.message_id} {msg.sender}: {msg.message} {msg.message_id} {msg.sender}: {msg.message}
</li> </li>
)); ));
return ( return (
<div ref={containerRef} className="flex flex-col h-full overflow-y-auto"> <div ref={containerRef} className="flex flex-col h-full overflow-y-auto">
<p className="text-center text-gray-400">{errorMessage}</p> <p className=" text-center text-gray-400">{errorMessage}</p>
<ul className="flex-grow list-none"> <ul className="flex-grow list-none">
{isLoading ? <LoadingWheel /> : null} {isLoading ? <LoadingWheel /> : null}
{messageList} {messageList}

View File

@@ -3,58 +3,49 @@ import logoutIcon from '../../../assets/logout.svg';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { UsernameType } from '../../utils/ProtectedRoutes.tsx'; import { UsernameType } from '../../utils/ProtectedRoutes.tsx';
import { useState } from 'react';
function UserProfile() { function UserProfile() {
const { username }: UsernameType = useOutletContext(); const { username }: UsernameType = useOutletContext();
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
function logout() { function logout() {
Cookies.remove('token'); Cookies.remove('token');
window.location.reload(); window.location.reload();
} }
return ( return (
<div className="relative inline-block"> <div className="dropdown dropdown-top">
<div <div
className="flex items-center cursor-pointer hs-dropdown-toggle" tabIndex={0}
onClick={toggleDropdown} className="flex items-center cursor-pointer"
role="button"
> >
<div className="flex items-center justify-center m-3 w-12 h-12 overflow-hidden"> <div className="flex items-center justify-center m-3 w-12 h-12 overflow-hidden">
<img src={zdjecie} alt="Profile image" className="w-8 h-8 invert" /> <img src={zdjecie} alt="Profile image" className="w-8 h-8 invert" />
</div> </div>
<div className="text-gray-200"> <div className="text-gray-200">
<p>{username}</p> <p>{username}</p>
</div> </div>
</div> </div>
{isOpen && ( <ul
<div className="absolute bottom-full left-0 mb-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5"> tabIndex={0}
<div className="m-2 dropdown-content menu p-2 shadow bg-gray-50 rounded-md w-52"
className="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
> >
<li>
<a <a
className="bg-green-50 cursor-pointer px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 flex" className="flex items-center px-4 py-2 text-sm text-black hover:bg-gray-200"
role="menuitem"
onClick={logout} onClick={logout}
> >
<img <img
className="w-5 mr-2" className="w-5 mr-2"
draggable={false} draggable={false}
src={logoutIcon} src={logoutIcon}
alt="log out ico" alt="Log out icon"
/> />
<p>Log out</p> <p>Log out</p>
</a> </a>
</div> </li>
</div> </ul>
)}
</div> </div>
); );
} }

View File

@@ -1,12 +1,21 @@
import daisyui from 'daisyui';
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
"./index.html", './index.html',
"./src/**/*.{js,ts,jsx,tsx}", './src/**/*.{js,ts,jsx,tsx}',
"node_modules/preline/dist/*.js", 'node_modules/preline/dist/*.js',
], ],
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [require("@tailwindcss/forms"), require("preline/plugin")], daisyui: {
themes: [],
},
plugins: [
require('@tailwindcss/forms'),
require('preline/plugin'),
require('daisyui'),
],
}; };

162
package-lock.json generated
View File

@@ -8,7 +8,8 @@
"name": "relay", "name": "relay",
"version": "0.0.1", "version": "0.0.1",
"devDependencies": { "devDependencies": {
"concurrently": "^9.0.1" "concurrently": "^9.0.1",
"daisyui": "^4.12.14"
} }
}, },
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
@@ -37,6 +38,16 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -128,6 +139,60 @@
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1" "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
} }
}, },
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/daisyui": {
"version": "4.12.14",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.14.tgz",
"integrity": "sha512-hA27cdBasdwd4/iEjn+aidoCrRroDuo3G5W9NDKaVCJI437Mm/3eSL/2u7MkZ0pt8a+TrYF3aT2pFVemTS3how==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -145,6 +210,13 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true,
"license": "MIT"
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -182,6 +254,83 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/require-directory": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -212,6 +361,17 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",

View File

@@ -8,8 +8,8 @@
"install:all": "concurrently \"npm install\" \"cd client && npm install\" \"cd server && npm install\"", "install:all": "concurrently \"npm install\" \"cd client && npm install\" \"cd server && npm install\"",
"build": "npm run build" "build": "npm run build"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^9.0.1" "concurrently": "^9.0.1",
"daisyui": "^4.12.14"
} }
} }