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,6 +155,12 @@ export const Chat = () => {
); );
})} })}
</div> </div>
{!selectedChat && (
<div role="alert" className="alert alert-info alert-outline">
<span>Wähle einen Nutzer aus und drücke auf + um einen Chat zu starten</span>
</div>
)}
{selectedChat && (
<div className="join"> <div className="join">
<div className="w-full"> <div className="w-full">
<label className="input join-item w-full"> <label className="input join-item w-full">
@@ -220,6 +219,7 @@ export const Chat = () => {
)} )}
</button> </button>
</div> </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",
{ userId, message },
({ error }: { error?: string }) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve(); 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({