Closed CaptainExorY closed 8 months ago
// React
import React, { useState, useEffect } from "react";
// CSS
import "./App.css";
// Cool :o
import { debugData } from "./utils/debugData";
import { fetchNui } from "./utils/fetchNui";
import { useNuiEvent } from "./hooks/useNuiEvent";
import { isEnvBrowser } from "./utils/misc";
import { motion } from "framer-motion";
// Mantine
import { Tabs, Transition } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { Cog, Info, MessageSquare, Send, Users } from "lucide-react";
import ChatTab from "./components/ChatTab";
import OnlineStaffTab from "./components/OnlineStaffTab";
import OnlineUsersTab from "./components/OnlineTestTab";
import SettingsTab from "./components/SettingsTab";
debugData([
{
action: "setVisible",
data: true,
},
]);
// debugData([
// {
// action: "staffchat:nui:firemessage",
// data: {
// adminData: {
// id: 1,
// name: "vipex",
// isStaff: true,
// },
// date_time: "N/A",
// inputData:
// "/image https://cdn.discordapp.com/attachments/839129248265666589/1178613078653415475/image2_1.jpg?ex=65800277&is=656d8d77&hm=3df933ad893f02e8e6ce3d5b610ba23ea478809799cb951158038bd58f8b0549&",
// },
// },
// ]);
interface Message {
adminData: StaffMember;
inputData: string;
date_time: string;
}
interface StaffMember {
id: string | number;
name: string;
// Unused but it's in there.
isStaff: boolean;
}
interface OnlineUsers {
id: string | number;
name: string;
// Unused but it's in there.
isStaff: boolean;
}
interface Settings {
theme: string;
notifications: boolean;
}
const initialSettings: Settings = {
theme: "default",
notifications: true,
};
const App: React.FC = () => {
const [visible, setVisible] = useState(false);
const [sourceData, setSourceData] = useState<StaffMember>({
id: 0,
name: "",
isStaff: false,
});
const [messages, setMessages] = useState<Message[]>([]);
const [activeStaff, setActiveStaff] = useState<StaffMember[]>([]);
const [activeUsers, setActiveUsers] = useState<OnlineUsers[]>([]);
const [settings, setSettings] = useState<Settings>(initialSettings);
debugData([
{
action: "staffchat:nui:admins",
data: [
{ isStaff: true, name: "vipex", id: "1" },
{ isStaff: true, name: "vipex_2", id: "2" },
{ isStaff: true, name: "vipex", id: "3" },
{ isStaff: true, name: "vipex_2", id: "4" },
{ isStaff: true, name: "vipex", id: "5" },
{ isStaff: true, name: "vipex_2", id: "6" },
{ isStaff: true, name: "vipex", id: "7" },
{ isStaff: true, name: "vipex_2", id: "8" },
],
},
]);
useNuiEvent("staffchat:clear", () => {
setMessages([]);
});
useNuiEvent<Settings>("staffchat:nui:settings", (psettings) => {
setSettings(psettings);
});
useNuiEvent("staffchat:nui:notify", (info) => {
if (!info) return;
notifications.show({
title: "Staffchat",
message: info,
icon: <Info size={"16px"} />,
});
});
useNuiEvent("staffchat:nui:sourcedata", setSourceData);
useNuiEvent<Message>("staffchat:nui:firemessage", (data) => {
if (!data) return;
setMessages([...messages, data]);
if (!visible && settings.notifications) {
notifications.show({
title: "StaffChat | Neue Nachricht!",
message: `${data.adminData.name} [ID - ${data.adminData.id}] hat eine Nachricht gesendet!`,
icon: <Send size={16} />,
});
}
});
// useNuiEvent("staffchat:nui:admins", (staff: StaffMember[]) => {
// console.log(JSON.stringify(staff));
// });
useNuiEvent("staffchat:nui:admins", setActiveStaff);
useNuiEvent("staffchat:nui:users", setActiveUsers);
useNuiEvent<boolean>("setVisible", setVisible);
useEffect(() => {
if (!visible) return;
const keyHandler = (e: KeyboardEvent) => {
if (["Escape"].includes(e.code)) {
if (!isEnvBrowser()) fetchNui("hideFrame");
else setVisible(!visible);
}
};
window.addEventListener("keydown", keyHandler);
return () => window.removeEventListener("keydown", keyHandler);
}, [visible]);
return (
<>
<Transition
mounted={visible}
duration={400}
transition={"slide-up"}
timingFunction="ease"
>
{(styles) => (
<div style={styles} className="absolute bottom-2 right-10">
<Tabs
color="dark"
variant="pills"
orientation="vertical"
styles={{
tab: {
transition: "0.4s",
},
}}
className={` ${
settings.theme === "default" ? "bg-[#2e2e2e]" : "bg-[#1a1a1a]"
} transition text-white rounded p-2 border border-[#1a1a1a] flex flex-row gap-10`}
defaultValue="chat"
>
<Tabs.List className="flex flex-col p-2 gap-2 font-bold">
<Tabs.Tab
value="chat"
leftSection={<MessageSquare className="bg-blue" size={16} />}
className="p-1"
>
Chat
</Tabs.Tab>
<Tabs.Tab value="onlineStaff" leftSection={<Users size={16} />}>
Teamler
</Tabs.Tab>
<Tabs.Tab value="onlineUsers" leftSection={<Users size={16} />}>
User
</Tabs.Tab>
<Tabs.Tab value="settings" leftSection={<Cog size={16} />}>
Einstellungen
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="chat">
<ChatTab
messages={messages}
sourceData={sourceData}
userSettings={settings}
/>
</Tabs.Panel>
<Tabs.Panel value="onlineStaff">
<OnlineStaffTab
staffMembers={activeStaff}
userSettings={settings}
/>
</Tabs.Panel>
<Tabs.Panel value="onlineUsers">
<OnlineUsersTab
userMembers={activeUsers}
userSettings={settings}
/>
</Tabs.Panel>
<Tabs.Panel value="settings">
<SettingsTab userSettings={settings} />
</Tabs.Panel>
</Tabs>
</div>
)}
</Transition>
</>
);
};
export default App;
import { User } from "lucide-react";
import "../App.css";
import { ScrollArea } from "@mantine/core";
interface Props {
userMembers: UserMember[];
userSettings: any;
}
interface Message {
playerData: UserMember;
inputData: string;
date_time: string;
}
interface UserMember {
id: string | number;
name: string;
isStaff: boolean;
}
const OnlineTestTab: React.FC<Props> = ({ userMembers, userSettings }) => {
return (
<>
<div
className={`w-[46dvh] h-[46dvh] ${
userSettings.theme === "default" ? "bg-[#1a1a1a]" : "bg-[#2a2a2a]"
} rounded bg-opacity-50 transition`}
>
<ScrollArea h={500}>
<div className=" m-1 grid gap-2 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{Object.values(userMembers).map(
(staff: UserMember, index: number) => {
if (!staff) return;
console.log(index);
return (
<div
className={`rounded-[2px] text-xs flex justify-between px-2 py-1 ${
userSettings.theme === "default"
? "bg-[#1a1a1a]"
: "bg-[#2a2a2a]"
}`}
key={index}
>
<p className="flex justify-center items-center">
{staff.name}
</p>
<p className="bg-blue-500 rounded-[2px] p-1 text-xs font-main text-opacity-50 font-medium">
ID: {staff.id}
</p>
</div>
);
}
)}
</div>
</ScrollArea>
</div>
</>
);
};
export default OnlineTestTab;
The core is now just for testing, not final
--server/core.lua
---@type adminData[]
AdminData = {}
---@type playerData[]
PlayerData = {}
---@param _source string
---@param _oldID string
AddEventHandler("playerJoining", function(_source, _oldID)
if source <= 0 then
return Debug("source is nil.")
end
local player = CPlayer:new(source)
if not player then
return Debug("CPlayer:new method is returning null.")
end
if player.isStaff then
AdminData[tostring(source)] = player
Debug(player.name, "was added to the AdminData table.")
PlayerData[tostring(source)] = player
Debug(player.name, "was added to the PlayerData table.")
else
PlayerData[tostring(source)] = player
Debug(player.name, "was added to the PlayerData table.")
end
end)
AddEventHandler("playerDropped", function(_reason)
if AdminData[tostring(source)] then
AdminData[tostring(source)] = nil
Debug("[netEvent:playerDropped] Event was triggered, and the player was removed from the AdminData table.")
else
PlayerData[tostring(source)] = nil
Debug("[netEvent:playerDropped] Event was triggered, and the player was removed from the PlayerData table.")
end
end)
SetTimeout(200, function()
local OnlinePlayers = GetPlayers()
Debug("AdminData table before looping through all players: ", json.encode(AdminData))
Debug("PlayerData table before looping through all players: ", json.encode(PlayerData))
for i = 1, #OnlinePlayers do
local playerSource = OnlinePlayers[i]
local player = CPlayer:new(playerSource)
if not player then
return Debug("[timeout:function] CPlayer:new is returning nil.")
end
if player.isStaff then
AdminData[tostring(playerSource)] = player
Debug(player.name, "was added to the AdminData table.")
PlayerData[tostring(playerSource)] = player
Debug(player.name, "was added to the PlayerData table.")
else
Debug("Nothing right now.")
end
end
Debug("AdminData table after looping through all of the players: ", json.encode(AdminData))
Debug("PlayerData table after looping through all of the players: ", json.encode(PlayerData))
end)
--server/events.lua
RegisterNetEvent("staffchat:server:admins", function()
if not source then
return Debug("[staffchat:server:admins] Event was called but source is nil.")
end
if not AdminData[tostring(source)] then
-- TODO: Notification system.
return Debug("[netEvent:staffchat:server:admins] Player is not a staff member.")
end
TriggerClientEvent("staffchat:client:admins", source, AdminData)
end)
RegisterNetEvent("staffchat:server:users", function()
if not source then
return Debug("[staffchat:server:users] Event was called but source is nil.")
end
if PlayerData[tostring(source)] then
-- TODO: Notification system.
return Debug("[netEvent:staffchat:server:users] Player is member.")
end
TriggerClientEvent("staffchat:client:users", source, PlayerData)
end)
---@param data messageInfo
RegisterNetEvent("staffchat:server:firemessage", function(data)
if not source or not AdminData[tostring(source)] then
return Debug("source is nil or the player isn't a staff member.")
end
if not next(data) then
return Debug("[netEvent:staffchat:server:firemessage] Event was called, but the first param is null/missing.")
end
data.adminData = AdminData[tostring(source)]
Debug("[netEvent:staffchat:server:firemessage] Data: ", json.encode(data))
for _, v in pairs(AdminData) do
---@diagnostic disable-next-line: param-type-mismatch
TriggerClientEvent("staffchat:client:firemessage", v.id, data)
end
end)
RegisterNetEvent("staffchat:server:permissions", function()
if not AdminData[tostring(source)] then
Debug("[netEvent:staffchat:server:permissions] Player is not staff.")
-- Not the best, but it works.
local exData = {
id = source,
name = GetPlayerName(source),
isStaff = false
}
TriggerClientEvent("staffchat:client:permissions", source, exData)
return
end
Debug("[netEvent:staffchat:server:permissions] AdminData[tostring(source)]:", json.encode(AdminData[tostring(source)]))
TriggerClientEvent("staffchat:client:permissions", source, AdminData[tostring(source)])
end)
-- client/events.lua
RegisterNetEvent("UIMessage", function(action, data)
UIMessage(action, data)
end)
RegisterNetEvent("staffchat:client:admins", function(data)
if not data or not next(data) then
return Debug("[staffchat:client:admins] Event was called but the first param null or not a table, param: ",
json.encode(data))
end
Debug("[staffchat:client:admins] Data param:", json.encode(data))
UIMessage("staffchat:nui:admins", data)
end)
RegisterNetEvent("staffchat:client:users", function(data)
if not data or not next(data) then
return Debug("[staffchat:client:users] Event was called but the first param null or not a table, param: ",
json.encode(data))
end
Debug("[staffchat:client:users] Data param:", json.encode(data))
UIMessage("staffchat:nui:users", data)
end)
RegisterNetEvent("staffchat:client:firemessage", function(data)
if not data or not next(data) then
return Debug("[staffchat:client:firemessage] Event was called but data parm is null or not a table, param: ",
json.encode(data))
end
Debug("[staffchat:client:firemessage] data param: ", json.encode(data))
UIMessage("staffchat:nui:firemessage", data)
end)
RegisterNetEvent("staffchat:client:permissions", function(data)
if not data or not next(data) then
return Debug("[staffchat:client:permissions] Event triggered, but the first param is null or not a table,param: ",
json.encode(data))
end
UIMessage("staffchat:nui:sourcedata", data)
PlayerData = data
end)
RegisterKeyMapping("staffchat", "Staff Chat", "keyboard", "F4")
I'm so sorry, I never got to this, I'm not on my Pc right now but if you need help then just send me a dm on discord/ @vipex.v
Im on my jurney of programming and currently working on your script. I wanted to add a User view and would love to get some guidiance from you. I already added a new list into the /server/core.lua where normal users that joins gets into.
Basically copied your code from /web/src/components/OnlineStaffTab.tsx to my file and changed the values
in the app.tsx: const App: React.FC = () => { const [visible, setVisible] = useState(false); const [sourceData, setSourceData] = useState({
id: 0,
name: "",
isStaff: false,
});
const [messages, setMessages] = useState<Message[]>([]);
const [activeStaff, setActiveStaff] = useState<StaffMember[]>([]);
const [activeUsers, setActiveUsers] = useState<OnlineUsers[]>([]); ////////MY LINE
and obviously changed more, copied your adminData in the shared/types.lua and named it playerData.. but im stuck here now...