enhanced overall Chat experience

This commit is contained in:
PxlLoewe
2025-06-09 22:38:31 -07:00
parent ea78b41510
commit b4b7b4def2
7 changed files with 119 additions and 98 deletions

View File

@@ -85,18 +85,18 @@ export const Chat = () => {
Keine Chatpartner gefunden Keine Chatpartner gefunden
</option> </option>
)} )}
{filteredDispatcher?.length || {(filteredDispatcher?.length || filteredAircrafts?.length) && (
(filteredAircrafts?.length && ( <option disabled value="default">
<option disabled value="default"> Chatpartner auswählen
Chatpartner auswählen </option>
</option> )}
))}
{filteredDispatcher?.map((dispatcher) => ( {filteredDispatcher?.map((dispatcher) => (
<option key={dispatcher.userId} value={dispatcher.userId}> <option key={dispatcher.userId} value={dispatcher.userId}>
{dispatcher.zone} - {asPublicUser(dispatcher.publicUser).fullName} {dispatcher.zone} - {asPublicUser(dispatcher.publicUser).fullName}
</option> </option>
))} ))}
{filteredAircrafts?.map((aircraft) => ( {filteredAircrafts?.map((aircraft) => (
<option key={aircraft.userId} value={aircraft.userId}> <option key={aircraft.userId} value={aircraft.userId}>
{aircraft.Station.bosCallsignShort} -{" "} {aircraft.Station.bosCallsignShort} -{" "}
@@ -111,7 +111,8 @@ export const Chat = () => {
const dispatcherUser = dispatcher?.find((d) => d.userId === addTabValue); const dispatcherUser = dispatcher?.find((d) => d.userId === addTabValue);
const user = aircraftUser || dispatcherUser; const user = aircraftUser || dispatcherUser;
if (!user) return; if (!user) return;
addChat(addTabValue, asPublicUser(user.publicUser).fullName); let role = "Station" in user ? user.Station.bosCallsignShort : user.zone;
addChat(addTabValue, `${asPublicUser(user.publicUser).fullName} (${role})`);
setSelectedChat(addTabValue); setSelectedChat(addTabValue);
}} }}
> >
@@ -124,21 +125,13 @@ export const Chat = () => {
if (!chat) return null; if (!chat) return null;
return ( return (
<Fragment key={userId}> <Fragment key={userId}>
<input <a
type="radio" className={cn("indicator tab", selectedChat === userId && "tab-active")}
className="tab" onClick={() => setSelectedChat(userId)}
aria-label={`<${chat.name}>`} >
checked={selectedChat === userId} {chat.name}
onClick={() => { {chat.notification && <span className="indicator-item status status-info" />}
setChatNotification(userId, false); </a>
}}
onChange={(e) => {
if (e.target.checked) {
// Handle tab change
setSelectedChat(userId);
}
}}
/>
<div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto"> <div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto">
{chat.messages.map((chatMessage) => { {chat.messages.map((chatMessage) => {
const isSender = chatMessage.senderId === session.data?.user.id; const isSender = chatMessage.senderId === session.data?.user.id;
@@ -162,64 +155,71 @@ export const Chat = () => {
); );
})} })}
</div> </div>
<div className="join"> {!selectedChat && (
<div className="w-full"> <div role="alert" className="alert alert-info alert-outline">
<label className="input join-item w-full"> <span>Wähle einen Nutzer aus und drücke auf + um einen Chat zu starten</span>
<input
type="text"
required
className="w-full"
onChange={(e) => {
setMessage(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
if (message.length < 1) return;
if (!selectedChat) return;
setSending(true);
sendMessage(selectedChat, message)
.then(() => {
setMessage("");
setSending(false);
})
.catch(() => {
setSending(false);
});
}
}}
value={message}
/>
</label>
</div> </div>
<button )}
className="btn btn-soft join-item" {selectedChat && (
onClick={(e) => { <div className="join">
e.preventDefault(); <div className="w-full">
if (message.length < 1) return; <label className="input join-item w-full">
if (!selectedChat) return; <input
setSending(true); type="text"
sendMessage(selectedChat, message) required
.then(() => { className="w-full"
setMessage(""); onChange={(e) => {
setSending(false); setMessage(e.target.value);
}) }}
.catch(() => { onKeyDown={(e) => {
setSending(false); if (e.key === "Enter" && !e.shiftKey) {
}); e.preventDefault();
return false; if (message.length < 1) return;
}} if (!selectedChat) return;
disabled={sending} setSending(true);
role="button" sendMessage(selectedChat, message)
onSubmit={() => false} // prevent submit event for react hook form .then(() => {
> setMessage("");
{sending ? ( setSending(false);
<span className="loading loading-spinner loading-sm"></span> })
) : ( .catch(() => {
<PaperPlaneIcon /> setSending(false);
)} });
</button> }
</div> }}
value={message}
/>
</label>
</div>
<button
className="btn btn-soft join-item"
onClick={(e) => {
e.preventDefault();
if (message.length < 1) return;
if (!selectedChat) return;
setSending(true);
sendMessage(selectedChat, message)
.then(() => {
setMessage("");
setSending(false);
})
.catch(() => {
setSending(false);
});
return false;
}}
disabled={sending}
role="button"
onSubmit={() => false} // prevent submit event for react hook form
>
{sending ? (
<span className="loading loading-spinner loading-sm"></span>
) : (
<PaperPlaneIcon />
)}
</button>
</div>
)}
</div> </div>
</div> </div>
)} )}

View File

@@ -372,6 +372,7 @@ export const MissionLayer = () => {
getMissionsAPI({ getMissionsAPI({
OR: [{ state: "draft" }, { state: "running" }], OR: [{ state: "draft" }, { state: "running" }],
}), }),
refetchInterval: 10_000,
}); });
const filteredMissions = useMemo(() => { const filteredMissions = useMemo(() => {

View File

@@ -46,7 +46,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
micDeviceId: null, micDeviceId: null,
speakingParticipants: [], speakingParticipants: [],
micVolume: 1, micVolume: 1,
state: "disconnected", state: "disconnected" as const,
remoteParticipants: 0, remoteParticipants: 0,
connectionQuality: ConnectionQuality.Unknown, connectionQuality: ConnectionQuality.Unknown,
room: null, room: null,
@@ -77,16 +77,23 @@ export const useAudioStore = create<TalkState>((set, get) => ({
set({ micDeviceId, micVolume }); set({ micDeviceId, micVolume });
}, },
toggleTalking: () => { toggleTalking: () => {
const { room, isTalking, micDeviceId, micVolume, speakingParticipants } = get(); const { room, isTalking, micDeviceId, micVolume, speakingParticipants, transmitBlocked } =
get();
if (!room) return; if (!room) return;
if (speakingParticipants.length > 0 && !isTalking) { if (speakingParticipants.length > 0 && !isTalking && !transmitBlocked) {
// Wenn andere sprechen, nicht reden // Wenn andere sprechen, nicht reden
set({ set({
message: "Rufgruppe besetzt", message: "Rufgruppe besetzt",
transmitBlocked: true, transmitBlocked: true,
}); });
return; return;
} else if (!isTalking && transmitBlocked) {
set({
message: null,
transmitBlocked: false,
});
return;
} }
// Todo: use micVolume // Todo: use micVolume
room.localParticipant.setMicrophoneEnabled(!isTalking, { room.localParticipant.setMicrophoneEnabled(!isTalking, {

View File

@@ -30,21 +30,30 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
chatOpen: false, chatOpen: false,
selectedChat: null, selectedChat: null,
setChatOpen: (open: boolean) => set({ chatOpen: open }), setChatOpen: (open: boolean) => set({ chatOpen: open }),
setSelectedChat: (chatId: string | null) => set({ selectedChat: chatId }), setSelectedChat: (chatId: string | null) => {
const { setChatNotification } = get();
set({ selectedChat: chatId });
if (chatId) {
setChatNotification(chatId, false); // Set notification to false when chat is selected
}
},
setOwnId: (id: string) => set({ ownId: id }), setOwnId: (id: string) => set({ ownId: id }),
chats: {}, chats: {},
sendMessage: (userId: string, message: string) => { sendMessage: (userId: string, message: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(dispatchSocket.connected){ if (dispatchSocket.connected) {
dispatchSocket.emit(
dispatchSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => { "send-message",
if (error) { { userId, message },
reject(error); ({ error }: { error?: string }) => {
} else { if (error) {
resolve(); reject(error);
} } else {
}); resolve();
} else if(pilotSocket.connected){ }
},
);
} else if (pilotSocket.connected) {
pilotSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => { pilotSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => {
if (error) { if (error) {
reject(error); reject(error);
@@ -66,7 +75,6 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
setChatNotification: (userId, notification) => { setChatNotification: (userId, notification) => {
const chat = get().chats[userId]; const chat = get().chats[userId];
if (!chat) return; if (!chat) return;
console.log("setChatNotification", userId, notification);
set((state) => { set((state) => {
return { return {
chats: { chats: {
@@ -95,7 +103,7 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
[userId]: { [userId]: {
...user, ...user,
name: isSender ? message.receiverName : message.senderName, name: isSender ? message.receiverName : message.senderName,
notification: !isSender && (state.selectedChat !== userId || !state.chatOpen), notification: state.selectedChat !== userId || !state.chatOpen,
messages: [...user.messages, message], // Neuen Zustand erzeugen messages: [...user.messages, message], // Neuen Zustand erzeugen
}, },
}, },

View File

@@ -16,7 +16,7 @@ export default async function RootLayout({
}>) { }>) {
const session = await getServerSession(); const session = await getServerSession();
if (!session || !session.user) { if (!session || !session.user.firstname) {
redirect("/login"); redirect("/login");
} }
if (!session.user.emailVerified) { if (!session.user.emailVerified) {

View File

@@ -53,7 +53,6 @@ export const Register = () => {
passwordConfirm: "", passwordConfirm: "",
}, },
}); });
console.log("Register form", form.formState.errors);
return ( return (
<form <form
className="card-body" className="card-body"
@@ -67,6 +66,10 @@ export const Register = () => {
firstname: form.getValues("firstname"), firstname: form.getValues("firstname"),
lastname: form.getValues("lastname"), lastname: form.getValues("lastname"),
}); });
if ("error" in user) {
toast.error(user.error);
return;
}
await sendVerificationLink(user.id); await sendVerificationLink(user.id);
await signIn("credentials", { await signIn("credentials", {
callbackUrl: "/", callbackUrl: "/",

View File

@@ -27,7 +27,9 @@ export const register = async ({ password, ...user }: Omit<Prisma.UserCreateInpu
}, },
}); });
if (existingUser) { if (existingUser) {
throw new Error("Ein Nutzer mit dieser E-Mail-Adresse existiert bereits."); return {
error: "Ein Nutzer mit dieser E-Mail-Adresse existiert bereits.",
};
} }
const newUser = prisma.user.create({ const newUser = prisma.user.create({