enhanced overall Chat experience
This commit is contained in:
@@ -85,18 +85,18 @@ export const Chat = () => {
|
||||
Keine Chatpartner gefunden
|
||||
</option>
|
||||
)}
|
||||
{filteredDispatcher?.length ||
|
||||
(filteredAircrafts?.length && (
|
||||
<option disabled value="default">
|
||||
Chatpartner auswählen
|
||||
</option>
|
||||
))}
|
||||
{(filteredDispatcher?.length || filteredAircrafts?.length) && (
|
||||
<option disabled value="default">
|
||||
Chatpartner auswählen
|
||||
</option>
|
||||
)}
|
||||
|
||||
{filteredDispatcher?.map((dispatcher) => (
|
||||
<option key={dispatcher.userId} value={dispatcher.userId}>
|
||||
{dispatcher.zone} - {asPublicUser(dispatcher.publicUser).fullName}
|
||||
</option>
|
||||
))}
|
||||
|
||||
{filteredAircrafts?.map((aircraft) => (
|
||||
<option key={aircraft.userId} value={aircraft.userId}>
|
||||
{aircraft.Station.bosCallsignShort} -{" "}
|
||||
@@ -111,7 +111,8 @@ export const Chat = () => {
|
||||
const dispatcherUser = dispatcher?.find((d) => d.userId === addTabValue);
|
||||
const user = aircraftUser || dispatcherUser;
|
||||
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);
|
||||
}}
|
||||
>
|
||||
@@ -124,21 +125,13 @@ export const Chat = () => {
|
||||
if (!chat) return null;
|
||||
return (
|
||||
<Fragment key={userId}>
|
||||
<input
|
||||
type="radio"
|
||||
className="tab"
|
||||
aria-label={`<${chat.name}>`}
|
||||
checked={selectedChat === userId}
|
||||
onClick={() => {
|
||||
setChatNotification(userId, false);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
// Handle tab change
|
||||
setSelectedChat(userId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<a
|
||||
className={cn("indicator tab", selectedChat === userId && "tab-active")}
|
||||
onClick={() => setSelectedChat(userId)}
|
||||
>
|
||||
{chat.name}
|
||||
{chat.notification && <span className="indicator-item status status-info" />}
|
||||
</a>
|
||||
<div className="tab-content bg-base-100 border-base-300 p-6 overflow-y-auto">
|
||||
{chat.messages.map((chatMessage) => {
|
||||
const isSender = chatMessage.senderId === session.data?.user.id;
|
||||
@@ -162,64 +155,71 @@ export const Chat = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="join">
|
||||
<div className="w-full">
|
||||
<label className="input join-item w-full">
|
||||
<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>
|
||||
{!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>
|
||||
<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>
|
||||
)}
|
||||
{selectedChat && (
|
||||
<div className="join">
|
||||
<div className="w-full">
|
||||
<label className="input join-item w-full">
|
||||
<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>
|
||||
<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>
|
||||
)}
|
||||
|
||||
@@ -372,6 +372,7 @@ export const MissionLayer = () => {
|
||||
getMissionsAPI({
|
||||
OR: [{ state: "draft" }, { state: "running" }],
|
||||
}),
|
||||
refetchInterval: 10_000,
|
||||
});
|
||||
|
||||
const filteredMissions = useMemo(() => {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
||||
micDeviceId: null,
|
||||
speakingParticipants: [],
|
||||
micVolume: 1,
|
||||
state: "disconnected",
|
||||
state: "disconnected" as const,
|
||||
remoteParticipants: 0,
|
||||
connectionQuality: ConnectionQuality.Unknown,
|
||||
room: null,
|
||||
@@ -77,16 +77,23 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
||||
set({ micDeviceId, micVolume });
|
||||
},
|
||||
toggleTalking: () => {
|
||||
const { room, isTalking, micDeviceId, micVolume, speakingParticipants } = get();
|
||||
const { room, isTalking, micDeviceId, micVolume, speakingParticipants, transmitBlocked } =
|
||||
get();
|
||||
if (!room) return;
|
||||
|
||||
if (speakingParticipants.length > 0 && !isTalking) {
|
||||
if (speakingParticipants.length > 0 && !isTalking && !transmitBlocked) {
|
||||
// Wenn andere sprechen, nicht reden
|
||||
set({
|
||||
message: "Rufgruppe besetzt",
|
||||
transmitBlocked: true,
|
||||
});
|
||||
return;
|
||||
} else if (!isTalking && transmitBlocked) {
|
||||
set({
|
||||
message: null,
|
||||
transmitBlocked: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Todo: use micVolume
|
||||
room.localParticipant.setMicrophoneEnabled(!isTalking, {
|
||||
|
||||
@@ -30,21 +30,30 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
|
||||
chatOpen: false,
|
||||
selectedChat: null,
|
||||
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 }),
|
||||
chats: {},
|
||||
sendMessage: (userId: string, message: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(dispatchSocket.connected){
|
||||
|
||||
dispatchSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} else if(pilotSocket.connected){
|
||||
if (dispatchSocket.connected) {
|
||||
dispatchSocket.emit(
|
||||
"send-message",
|
||||
{ userId, message },
|
||||
({ error }: { error?: string }) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (pilotSocket.connected) {
|
||||
pilotSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
@@ -66,7 +75,6 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
|
||||
setChatNotification: (userId, notification) => {
|
||||
const chat = get().chats[userId];
|
||||
if (!chat) return;
|
||||
console.log("setChatNotification", userId, notification);
|
||||
set((state) => {
|
||||
return {
|
||||
chats: {
|
||||
@@ -95,7 +103,7 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
|
||||
[userId]: {
|
||||
...user,
|
||||
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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async function RootLayout({
|
||||
}>) {
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session || !session.user) {
|
||||
if (!session || !session.user.firstname) {
|
||||
redirect("/login");
|
||||
}
|
||||
if (!session.user.emailVerified) {
|
||||
|
||||
@@ -53,7 +53,6 @@ export const Register = () => {
|
||||
passwordConfirm: "",
|
||||
},
|
||||
});
|
||||
console.log("Register form", form.formState.errors);
|
||||
return (
|
||||
<form
|
||||
className="card-body"
|
||||
@@ -67,6 +66,10 @@ export const Register = () => {
|
||||
firstname: form.getValues("firstname"),
|
||||
lastname: form.getValues("lastname"),
|
||||
});
|
||||
if ("error" in user) {
|
||||
toast.error(user.error);
|
||||
return;
|
||||
}
|
||||
await sendVerificationLink(user.id);
|
||||
await signIn("credentials", {
|
||||
callbackUrl: "/",
|
||||
|
||||
@@ -27,7 +27,9 @@ export const register = async ({ password, ...user }: Omit<Prisma.UserCreateInpu
|
||||
},
|
||||
});
|
||||
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({
|
||||
|
||||
Reference in New Issue
Block a user