Global Messages
This commit is contained in:
107
apps/hub/app/(app)/admin/message/_components/messageForm.tsx
Normal file
107
apps/hub/app/(app)/admin/message/_components/messageForm.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Message } from "@repo/db";
|
||||||
|
import { MessageOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { addMessage, disableMessage } from "../action";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export const MessageForm = ({ message }: { message?: Message }) => {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const getDefaultShowUntilDate = () => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() + 5);
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableMessageClient = async () => {
|
||||||
|
disableMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof MessageOptionalDefaultsSchema>>({
|
||||||
|
resolver: zodResolver(MessageOptionalDefaultsSchema),
|
||||||
|
defaultValues: {
|
||||||
|
message: message?.message,
|
||||||
|
color: message?.color,
|
||||||
|
isMainMsg: true,
|
||||||
|
active: true,
|
||||||
|
showUntil: getDefaultShowUntilDate(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(async (values) => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
const msg = await addMessage(values);
|
||||||
|
} catch (error) {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
console.error("Failed to add message", error);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
className="grid grid-cols-6 gap-3"
|
||||||
|
>
|
||||||
|
<label className="floating-label col-span-6">
|
||||||
|
<span>Globale Service Nachricht</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Globale Service Nachricht"
|
||||||
|
className="input input-md w-full mb-2"
|
||||||
|
{...form.register("message")}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div className="gap-2 flex justify-between col-span-6">
|
||||||
|
<div className="content-center">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value="INFO"
|
||||||
|
{...form.register("color")}
|
||||||
|
className="radio radio-info ml-2 mr-2"
|
||||||
|
/>
|
||||||
|
<span>Info</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value="SUCCESS"
|
||||||
|
{...form.register("color")}
|
||||||
|
className="radio radio-success ml-2 mr-2"
|
||||||
|
/>
|
||||||
|
<span>Success</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value="WARNING"
|
||||||
|
{...form.register("color")}
|
||||||
|
className="radio radio-warning ml-2 mr-2"
|
||||||
|
/>
|
||||||
|
<span>Warning</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value="ERROR"
|
||||||
|
{...form.register("color")}
|
||||||
|
className="radio radio-error ml-2 mr-2"
|
||||||
|
/>
|
||||||
|
<span>Error</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-soft mr-2"
|
||||||
|
onClick={disableMessageClient}
|
||||||
|
>
|
||||||
|
Aktuelle Nachricht deaktivieren
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Speichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
36
apps/hub/app/(app)/admin/message/action.tsx
Normal file
36
apps/hub/app/(app)/admin/message/action.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"use server";
|
||||||
|
import { prisma, Prisma } from "@repo/db";
|
||||||
|
|
||||||
|
export const addMessage = async (message: Prisma.MessageCreateInput) => {
|
||||||
|
try {
|
||||||
|
// Set all current messages with isMainMsg=true to active=false
|
||||||
|
await prisma.message.updateMany({
|
||||||
|
where: { isMainMsg: true },
|
||||||
|
data: { active: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.message.create({
|
||||||
|
data: {
|
||||||
|
message: message.message,
|
||||||
|
color: message.color,
|
||||||
|
isMainMsg: true,
|
||||||
|
active: true,
|
||||||
|
showUntil: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to add message");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const disableMessage = async () => {
|
||||||
|
try {
|
||||||
|
// Set all current messages with isMainMsg=true to active=false
|
||||||
|
await prisma.message.updateMany({
|
||||||
|
where: { isMainMsg: true },
|
||||||
|
data: { active: false },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to disable message");
|
||||||
|
}
|
||||||
|
};
|
||||||
19
apps/hub/app/(app)/admin/message/page.tsx
Normal file
19
apps/hub/app/(app)/admin/message/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { MessageSquareWarning } from "lucide-react";
|
||||||
|
import { MessageForm } from "./_components/messageForm";
|
||||||
|
|
||||||
|
export default function MessagePage() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-6 gap-4">
|
||||||
|
<div className="col-span-full">
|
||||||
|
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
||||||
|
<MessageSquareWarning className="w-5 h-5" /> Service Nachrichten
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6">
|
||||||
|
<div className="card-body">
|
||||||
|
<MessageForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,10 +7,11 @@ import {
|
|||||||
ReaderIcon,
|
ReaderIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { WarningAlert } from "./ui/PageAlert";
|
||||||
|
|
||||||
export const VerticalNav = () => {
|
export const VerticalNav = () => {
|
||||||
return (
|
return (
|
||||||
<ul className="menu w-64 bg-base-300 p-4 rounded-lg shadow-md">
|
<ul className="menu w-64 bg-base-300 p-3 rounded-lg shadow-md font-semibold">
|
||||||
<li>
|
<li>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<HomeIcon /> Dashboard
|
<HomeIcon /> Dashboard
|
||||||
@@ -50,6 +51,9 @@ export const VerticalNav = () => {
|
|||||||
<li>
|
<li>
|
||||||
<Link href="/admin/event">Events</Link>
|
<Link href="/admin/event">Events</Link>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/admin/message">Service Nachrichten</Link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
@@ -63,6 +67,7 @@ export const HorizontalNav = () => (
|
|||||||
<a className="btn btn-ghost normal-case text-xl">
|
<a className="btn btn-ghost normal-case text-xl">
|
||||||
Virtual Air Rescue - HUB
|
Virtual Air Rescue - HUB
|
||||||
</a>
|
</a>
|
||||||
|
<WarningAlert />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center ml-auto">
|
<div className="flex items-center ml-auto">
|
||||||
<ul className="flex space-x-2 px-1">
|
<ul className="flex space-x-2 px-1">
|
||||||
|
|||||||
57
apps/hub/app/_components/ui/PageAlert.tsx
Normal file
57
apps/hub/app/_components/ui/PageAlert.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { prisma } from "@repo/db";
|
||||||
|
const fetchMainMessage = async () => {
|
||||||
|
return await prisma.message.findFirst({
|
||||||
|
where: {
|
||||||
|
active: true,
|
||||||
|
isMainMsg: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mainMessage = await fetchMainMessage();
|
||||||
|
|
||||||
|
let msgColor;
|
||||||
|
switch (mainMessage?.color) {
|
||||||
|
case "WARNING":
|
||||||
|
msgColor = "alert alert-warning ml-3";
|
||||||
|
break;
|
||||||
|
case "INFO":
|
||||||
|
msgColor = "alert alert-info ml-3";
|
||||||
|
break;
|
||||||
|
case "SUCCESS":
|
||||||
|
msgColor = "alert alert-success ml-3";
|
||||||
|
break;
|
||||||
|
case "ERROR":
|
||||||
|
msgColor = "alert alert-error ml-3";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
msgColor = "alert ml-3";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WarningAlert = () => {
|
||||||
|
if (mainMessage?.message == "" || !mainMessage) {
|
||||||
|
return <></>;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div role="alert" className={msgColor}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-6 w-6 shrink-0 stroke-current"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="font-bold">{mainMessage?.message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ACHTUNG! Wir führen am kommenden Samstag Wartungsarbeiten am Server
|
||||||
|
durch, von 19:00 Uhr bis 19:30 Uhr wird das HUB nicht erreichbar sein. */
|
||||||
19
packages/database/prisma/schema/message.prisma
Normal file
19
packages/database/prisma/schema/message.prisma
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
enum GlobalColor {
|
||||||
|
PRIMARY
|
||||||
|
SECONDARY
|
||||||
|
INFO
|
||||||
|
SUCCESS
|
||||||
|
WARNING
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
model Message {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
color GlobalColor
|
||||||
|
message String
|
||||||
|
showUntil DateTime
|
||||||
|
isMainMsg Boolean
|
||||||
|
active Boolean
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user