feat(admin): Added shoutout to admin panel (#6346)

* Added shoutout

* Added shoutout function

* Fixed test.

* Included feedback from review.

* Removed unnecessary file
This commit is contained in:
SamTV12345 2024-04-21 17:58:51 +02:00 committed by GitHub
parent d64924e9f5
commit e12be96102
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 467 additions and 214 deletions

View file

@ -9,19 +9,12 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {},
"dependencies": {
"@radix-ui/react-switch": "^1.0.3"
},
"devDependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-toast": "^1.1.5",
"i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1",
"lucide-react": "^0.372.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3",
"zustand": "^4.5.2",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@typescript-eslint/eslint-plugin": "^7.7.0",
@ -30,10 +23,19 @@
"eslint": "^9.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1",
"lucide-react": "^0.372.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.3",
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3",
"socket.io-client": "^4.7.5",
"typescript": "^5.4.5",
"vite": "^5.2.9",
"vite-plugin-static-copy": "^1.0.3",
"vite-plugin-svgr": "^4.2.0"
"vite-plugin-svgr": "^4.2.0",
"zustand": "^4.5.2"
}
}

View file

@ -6,7 +6,7 @@ import {NavLink, Outlet, useNavigate} from "react-router-dom";
import {useStore} from "./store/store.ts";
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
import {Trans, useTranslation} from "react-i18next";
import {Cable, Construction, Crown, NotepadText, Wrench} from "lucide-react";
import {Cable, Construction, Crown, NotepadText, Wrench, PhoneCall} from "lucide-react";
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
export const App = ()=> {
@ -96,8 +96,10 @@ export const App = ()=> {
<ul>
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
<li> <NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
<li><NavLink to={"/pads"}><NotepadText/><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
<li><NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
<li><NavLink to={"/pads"}><NotepadText/><Trans
i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
<li><NavLink to={"/shout"}><PhoneCall/>Communication</NavLink></li>
</ul>
</div>
</div>

View file

@ -0,0 +1,13 @@
export type ShoutType = {
type: string,
data:{
type: string,
payload: {
message: {
message: string,
sticky: boolean
},
timestamp: number
}
}
}

View file

@ -604,6 +604,25 @@ pre {
outline: none;
}
.send-message {
position: relative;
}
.send-message input {
width: auto;
}
.send-message {
}
.send-message svg {
position: absolute;
right: 3px;
bottom: -3px;
left: auto !important;
}
.search-field svg {
position: absolute;
left: 3px;
@ -733,3 +752,52 @@ input, button, select, optgroup, textarea {
right: 10px;
color: #666;
}
.SwitchRoot {
align-self: center;
width: 60px;
height: 30px;
background-color: black;
border-radius: 9999px;
position: relative;
box-shadow: 0 2px 10px var(--black-a7);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.SwitchRoot:focus {
box-shadow: 0 0 0 2px black;
}
.SwitchRoot[data-state='checked'] {
background-color: var(--etherpad-color);
}
.SwitchThumb {
display: block;
width: 20px;
height: 20px;
background-color: white;
border-radius: 9999px;
box-shadow: 0 2px 2px var(--black-a7);
transition: transform 100ms;
transform: translateX(2px);
will-change: transform;
}
.SwitchThumb[data-state='checked'] {
transform: translateX(25px);
}
.Label {
color: white;
font-size: 15px;
line-height: 1;
}
.message {
position: relative;
padding: 10px;
border: 1px solid #e0e0e0;
margin: 10px 20px 10px 10px;
border-radius: 10px 0 10px 10px;
background-color: var(--etherpad-color);
color: white
}

View file

@ -12,6 +12,7 @@ import {I18nextProvider} from "react-i18next";
import i18n from "./localization/i18n.ts";
import {PadPage} from "./pages/PadPage.tsx";
import {ToastDialog} from "./utils/Toast.tsx";
import {ShoutPage} from "./pages/ShoutPage.tsx";
const router = createBrowserRouter(createRoutesFromElements(
<><Route element={<App/>}>
@ -20,6 +21,7 @@ const router = createBrowserRouter(createRoutesFromElements(
<Route path="/settings" element={<SettingsPage/>}/>
<Route path="/help" element={<HelpPage/>}/>
<Route path="/pads" element={<PadPage/>}/>
<Route path="/shout" element={<ShoutPage/>}/>
</Route><Route path="/login">
<Route index element={<LoginScreen/>}/>
</Route></>

View file

@ -0,0 +1,76 @@
import {useEffect, useState} from "react";
import {SendHorizonal} from 'lucide-react'
import {useStore} from "../store/store.ts";
import * as Switch from '@radix-ui/react-switch';
import {ShoutType} from "../components/ShoutType.ts";
export const ShoutPage = ()=>{
const [totalUsers, setTotalUsers] = useState(0);
const [message, setMessage] = useState<string>("");
const [sticky, setSticky] = useState<boolean>(false);
const socket = useStore(state => state.settingsSocket);
const [shouts, setShouts] = useState<ShoutType[]>([]);
useEffect(() => {
fetch('/stats')
.then(response => response.json())
.then(data => setTotalUsers(data.totalUsers));
}, []);
useEffect(() => {
if(socket) {
socket.on('shout', (shout) => {
setShouts([...shouts, shout])
})
}
}, [socket, shouts])
const sendMessage = () => {
socket?.emit('shout', {
message,
sticky
});
setMessage('')
}
return (
<div>
<h1>Communication</h1>
{totalUsers > 0 && <p>There {totalUsers>1?"are":"is"} currently {totalUsers} user{totalUsers>1?"s":""} online</p>}
<div style={{height: '80vh', display: 'flex', flexDirection: 'column'}}>
<div style={{flexGrow: 1, backgroundColor: 'white', overflowY: "auto"}}>
{
shouts.map((shout) => {
return (
<div key={shout.data.payload.timestamp} className="message">
<div>{shout.data.payload.message.message}</div>
<div style={{display: 'flex'}}>
<div style={{flexGrow: 1}}></div>
<div
style={{color: "lightgray"}}>{new Date(shout.data.payload.timestamp).toLocaleTimeString()
+ " " + new Date(shout.data.payload.timestamp).toLocaleDateString()}</div>
</div>
</div>
)
})
}
</div>
<form onSubmit={(e) => {
e.preventDefault()
sendMessage()
}} className="send-message search-field" style={{display: 'flex', gap: '10px'}}>
<Switch.Root title="Change sticky message" className="SwitchRoot" checked={sticky}
onCheckedChange={() => {
setSticky(!sticky);
}}>
<Switch.Thumb className="SwitchThumb"/>
</Switch.Root>
<input required value={message} onChange={v=>setMessage(v.target.value)}
style={{width: '100%', paddingRight: '55px', backgroundColor: '#e0e0e0', flexGrow: 1}}/>
<SendHorizonal style={{bottom: '5px', right: '9px', color: '#0f775b'}} onClick={()=>sendMessage()}/>
</form>
</div>
</div>
)
}

View file

@ -28,8 +28,11 @@ export default defineConfig({
'/admin-auth/': {
target: 'http://localhost:9001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
},
'/stats': {
target: 'http://localhost:9001',
changeOrigin: true,
}
}
}
}
})