Improved Changelog, Changelog in Dispatch
This commit is contained in:
@@ -7,14 +7,22 @@ import AdminPanel from "_components/navbar/AdminPanel";
|
|||||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||||
import { WarningAlert } from "_components/navbar/PageAlert";
|
import { WarningAlert } from "_components/navbar/PageAlert";
|
||||||
import { Radar } from "lucide-react";
|
import { Radar } from "lucide-react";
|
||||||
|
import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper";
|
||||||
|
import { prisma } from "@repo/db";
|
||||||
|
|
||||||
export default async function Navbar() {
|
export default async function Navbar() {
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
|
const latestChangelog = await prisma.changelog.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="navbar bg-base-100 flex justify-between gap-5 shadow-sm">
|
<div className="navbar bg-base-100 flex justify-between gap-5 shadow-sm">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-xl font-semibold normal-case">VAR Leitstelle V2</p>
|
<p className="text-xl font-semibold normal-case">VAR Leitstelle</p>
|
||||||
|
<ChangelogWrapper latestChangelog={latestChangelog} />
|
||||||
{session?.user.permissions.includes("ADMIN_KICK") && <AdminPanel />}
|
{session?.user.permissions.includes("ADMIN_KICK") && <AdminPanel />}
|
||||||
</div>
|
</div>
|
||||||
<WarningAlert />
|
<WarningAlert />
|
||||||
|
|||||||
@@ -5,12 +5,20 @@ import Link from "next/link";
|
|||||||
import { Settings } from "./_components/Settings";
|
import { Settings } from "./_components/Settings";
|
||||||
import { WarningAlert } from "_components/navbar/PageAlert";
|
import { WarningAlert } from "_components/navbar/PageAlert";
|
||||||
import { Radar } from "lucide-react";
|
import { Radar } from "lucide-react";
|
||||||
|
import { prisma } from "@repo/db";
|
||||||
|
import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper";
|
||||||
|
|
||||||
export default function Navbar() {
|
export default async function Navbar() {
|
||||||
|
const latestChangelog = await prisma.changelog.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div className="navbar bg-base-100 flex justify-between gap-5 shadow-sm">
|
<div className="navbar bg-base-100 flex justify-between gap-5 shadow-sm">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-xl font-semibold normal-case">VAR Operations Center</p>
|
<p className="text-xl font-semibold normal-case">VAR Operations Center</p>
|
||||||
|
<ChangelogWrapper latestChangelog={latestChangelog} />
|
||||||
</div>
|
</div>
|
||||||
<WarningAlert />
|
<WarningAlert />
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export const SmartPopup = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleConflict = useCallback(() => {
|
const handleConflict = useCallback(() => {
|
||||||
console.log("handleConflict in smartMarker", id, options);
|
|
||||||
const newAnchor = calculateAnchor(id, "popup", options);
|
const newAnchor = calculateAnchor(id, "popup", options);
|
||||||
setAnchor(newAnchor);
|
setAnchor(newAnchor);
|
||||||
}, [id, options]);
|
}, [id, options]);
|
||||||
|
|||||||
34
apps/dispatch/app/_components/navbar/ChangelogWrapper.tsx
Normal file
34
apps/dispatch/app/_components/navbar/ChangelogWrapper.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
import { Changelog } from "@repo/db";
|
||||||
|
import { ChangelogModalBtn } from "@repo/shared-components";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { editUserAPI } from "_querys/user";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
export const ChangelogWrapper = ({ latestChangelog }: { latestChangelog: Changelog | null }) => {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const editUserMutation = useMutation({
|
||||||
|
mutationFn: editUserAPI,
|
||||||
|
});
|
||||||
|
|
||||||
|
const autoOpen = !session?.user.changelogAck && !!latestChangelog;
|
||||||
|
|
||||||
|
if (!latestChangelog) return null;
|
||||||
|
if (!session) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChangelogModalBtn
|
||||||
|
hideIcon
|
||||||
|
className="text-sm text-gray-500"
|
||||||
|
latestChangelog={latestChangelog}
|
||||||
|
autoOpen={autoOpen}
|
||||||
|
onClose={async () => {
|
||||||
|
await editUserMutation.mutateAsync({ id: session?.user.id, user: { changelogAck: true } });
|
||||||
|
if (!session?.user.changelogAck) {
|
||||||
|
toast.success("Changelog als gelesen markiert");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.13.1",
|
"packageManager": "pnpm@10.13.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint --max-warnings 0",
|
"lint": "next lint --max-warnings 0",
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { Button } from "@repo/shared-components";
|
|
||||||
import MDEditor from "@uiw/react-md-editor";
|
|
||||||
import { RefreshCw } from "lucide-react";
|
|
||||||
import { updateChangelogAck } from "./ChangelogActions";
|
|
||||||
import { Changelog } from "@repo/db";
|
|
||||||
|
|
||||||
export const ChangelogModal = ({
|
|
||||||
latestChangelog,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
latestChangelog: Changelog | null;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
if (!isOpen || !latestChangelog) return null;
|
|
||||||
|
|
||||||
const handleClose = async () => {
|
|
||||||
onClose();
|
|
||||||
await updateChangelogAck();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="changelogModalToggle"
|
|
||||||
className="modal-toggle"
|
|
||||||
onChange={handleClose}
|
|
||||||
/>
|
|
||||||
<dialog open className="modal p-4">
|
|
||||||
<div className="modal-box max-h-11/12 w-11/12 max-w-2xl overflow-y-auto">
|
|
||||||
<form method="dialog">
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<h3 className="flex items-center gap-2 text-lg font-bold">
|
|
||||||
<span className="text-primary">{latestChangelog.title}</span> ist nun Verfügbar!
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
{latestChangelog.previewImage && (
|
|
||||||
<Image
|
|
||||||
src={latestChangelog.previewImage}
|
|
||||||
alt="Preview"
|
|
||||||
width={800}
|
|
||||||
height={400}
|
|
||||||
className="mt-4 h-auto w-full object-cover"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-base-content/80 mb-2 mt-4 text-left">
|
|
||||||
<MDEditor.Markdown
|
|
||||||
source={latestChangelog.text}
|
|
||||||
style={{
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="modal-action">
|
|
||||||
<Button className="btn btn-info btn-outline" onClick={handleClose}>
|
|
||||||
Weiter zum HUB
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label className="modal-backdrop" htmlFor="changelogModalToggle">
|
|
||||||
Close
|
|
||||||
</label>
|
|
||||||
</dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChangelogBtn = ({ latestChangelog }: { latestChangelog: Changelog | null }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
if (!latestChangelog) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<a
|
|
||||||
href="#!"
|
|
||||||
className="hover:text-primary flex items-center gap-1"
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
>
|
|
||||||
<RefreshCw size={12} /> {latestChangelog.title}
|
|
||||||
</a>
|
|
||||||
<ChangelogModal
|
|
||||||
latestChangelog={latestChangelog}
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={() => setIsOpen(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OpenChangelogOnPageload = ({
|
|
||||||
latestChangelog,
|
|
||||||
}: {
|
|
||||||
latestChangelog: Changelog | null;
|
|
||||||
}) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
|
||||||
|
|
||||||
if (!latestChangelog) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ChangelogModal
|
|
||||||
latestChangelog={latestChangelog}
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={() => setIsOpen(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import { prisma } from "@repo/db";
|
|
||||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
|
||||||
|
|
||||||
export async function getLatestChangelog() {
|
|
||||||
try {
|
|
||||||
const latestChangelog = await prisma.changelog.findMany({
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (latestChangelog.length > 0 && latestChangelog[0]) {
|
|
||||||
return latestChangelog[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch latest changelog:", error);
|
|
||||||
throw new Error("Failed to fetch latest changelog");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateChangelogAck() {
|
|
||||||
const session = await getServerSession();
|
|
||||||
if (session?.user) {
|
|
||||||
await prisma.user.update({
|
|
||||||
where: { id: session.user.id },
|
|
||||||
data: { changelogAck: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
apps/hub/app/(app)/_components/ChangelogWrapper.tsx
Normal file
26
apps/hub/app/(app)/_components/ChangelogWrapper.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
import { updateUser } from "(app)/settings/actions";
|
||||||
|
import { Changelog } from "@repo/db";
|
||||||
|
import { ChangelogModalBtn } from "@repo/shared-components";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
export const ChangelogWrapper = ({ latestChangelog }: { latestChangelog: Changelog | null }) => {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const autoOpen = !session?.user.changelogAck && !!latestChangelog;
|
||||||
|
|
||||||
|
if (!latestChangelog) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChangelogModalBtn
|
||||||
|
latestChangelog={latestChangelog}
|
||||||
|
autoOpen={autoOpen}
|
||||||
|
onClose={async () => {
|
||||||
|
await updateUser({ changelogAck: true });
|
||||||
|
if (!session?.user.changelogAck) {
|
||||||
|
toast.success("Changelog als gelesen markiert");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,11 +2,22 @@ import Image from "next/image";
|
|||||||
import { DiscordLogoIcon, InstagramLogoIcon, ReaderIcon } from "@radix-ui/react-icons";
|
import { DiscordLogoIcon, InstagramLogoIcon, ReaderIcon } from "@radix-ui/react-icons";
|
||||||
import YoutubeSvg from "./youtube_wider.svg";
|
import YoutubeSvg from "./youtube_wider.svg";
|
||||||
import FacebookSvg from "./facebook.svg";
|
import FacebookSvg from "./facebook.svg";
|
||||||
import { ChangelogBtn } from "./Changelog";
|
import { ChangelogModalBtn } from "@repo/shared-components";
|
||||||
import { getLatestChangelog } from "./ChangelogActions";
|
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||||
|
import { updateUser } from "(app)/settings/actions";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { ChangelogWrapper } from "(app)/_components/ChangelogWrapper";
|
||||||
|
import { prisma } from "@repo/db";
|
||||||
|
|
||||||
export const Footer = async () => {
|
export const Footer = async () => {
|
||||||
const latestChangelog = await getLatestChangelog();
|
const session = await getServerSession();
|
||||||
|
const latestChangelog = await prisma.changelog.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const autoOpen = !session?.user.changelogAck && !!latestChangelog;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="footer bg-base-200 mt-4 flex items-center justify-between rounded-lg p-4 shadow-md">
|
<footer className="footer bg-base-200 mt-4 flex items-center justify-between rounded-lg p-4 shadow-md">
|
||||||
@@ -18,7 +29,7 @@ export const Footer = async () => {
|
|||||||
<a href="https://virtualairrescue.com/datenschutz/" className="hover:text-primary">
|
<a href="https://virtualairrescue.com/datenschutz/" className="hover:text-primary">
|
||||||
Datenschutzerklärung
|
Datenschutzerklärung
|
||||||
</a>
|
</a>
|
||||||
<ChangelogBtn latestChangelog={latestChangelog} />
|
<ChangelogWrapper latestChangelog={latestChangelog} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Center: Copyright */}
|
{/* Center: Copyright */}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { useEffect, useState } from "react";
|
|||||||
import { deleteChangelog, upsertChangelog } from "../action";
|
import { deleteChangelog, upsertChangelog } from "../action";
|
||||||
import { Button } from "../../../../_components/ui/Button";
|
import { Button } from "../../../../_components/ui/Button";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import Image from "next/image";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
@@ -73,7 +72,13 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
|||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
<FileText className="h-5 w-5" /> Allgemeines
|
<FileText className="h-5 w-5" /> Allgemeines
|
||||||
</h2>
|
</h2>
|
||||||
<Input form={form} label="Titel" name="title" className="input-sm" />
|
<Input
|
||||||
|
form={form}
|
||||||
|
label="Titel"
|
||||||
|
name="title"
|
||||||
|
placeholder="Titel (vX.X.X)"
|
||||||
|
className="input-sm"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
form={form}
|
form={form}
|
||||||
label="Bild-URL"
|
label="Bild-URL"
|
||||||
@@ -91,7 +96,7 @@ export const ChangelogForm = ({ changelog }: { changelog?: Changelog }) => {
|
|||||||
{(() => {
|
{(() => {
|
||||||
if (showImage && isValidImageUrl(previewImage) && !imageError) {
|
if (showImage && isValidImageUrl(previewImage) && !imageError) {
|
||||||
return (
|
return (
|
||||||
<Image
|
<img
|
||||||
src={previewImage}
|
src={previewImage}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
width={200}
|
width={200}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { prisma, Prisma, Changelog } from "@repo/db";
|
import { prisma, Prisma, Changelog } from "@repo/db";
|
||||||
|
|
||||||
export const upsertChangelog = async (
|
export const upsertChangelog = async (
|
||||||
changelog: Prisma.ChangelogCreateInput,
|
changelog: Prisma.ChangelogCreateInput,
|
||||||
id?: Changelog["id"],
|
id?: Changelog["id"],
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { getServerSession } from "../api/auth/[...nextauth]/auth";
|
|||||||
import { EmailVerification } from "_components/EmailVerification";
|
import { EmailVerification } from "_components/EmailVerification";
|
||||||
import { FirstPath } from "./_components/FirstPath";
|
import { FirstPath } from "./_components/FirstPath";
|
||||||
import { Penalty } from "_components/Penalty";
|
import { Penalty } from "_components/Penalty";
|
||||||
import { getLatestChangelog } from "(app)/_components/ChangelogActions";
|
|
||||||
import { OpenChangelogOnPageload } from "(app)/_components/Changelog";
|
|
||||||
|
|
||||||
import { Footer } from "(app)/_components/Footer";
|
import { Footer } from "(app)/_components/Footer";
|
||||||
|
|
||||||
@@ -24,8 +22,6 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
if (!session) redirect(`/login`);
|
if (!session) redirect(`/login`);
|
||||||
|
|
||||||
const latestChangelog = !session.user.changelogAck ? await getLatestChangelog() : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="hero min-h-screen"
|
className="hero min-h-screen"
|
||||||
@@ -54,9 +50,7 @@ export default async function RootLayout({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!session.user.pathSelected && <FirstPath />}
|
{!session.user.pathSelected && <FirstPath />}
|
||||||
{session.user.pathSelected && latestChangelog && (
|
|
||||||
<OpenChangelogOnPageload latestChangelog={latestChangelog} />
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.13.1",
|
"packageManager": "pnpm@10.13.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
|||||||
102
packages/shared-components/components/Changelog.tsx
Normal file
102
packages/shared-components/components/Changelog.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button, cn } from "@repo/shared-components";
|
||||||
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
import { Changelog } from "@repo/db";
|
||||||
|
|
||||||
|
export const ChangelogModal = ({
|
||||||
|
latestChangelog,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
latestChangelog: Changelog;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<dialog open={isOpen} className="modal p-4">
|
||||||
|
<div className="modal-box max-h-11/12 w-11/12 max-w-2xl overflow-y-auto">
|
||||||
|
<form method="dialog">
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<h3 className="flex items-center gap-2 text-lg font-bold">
|
||||||
|
<span className="text-primary">{latestChangelog.title}</span> ist nun Verfügbar!
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
{latestChangelog.previewImage && (
|
||||||
|
<img
|
||||||
|
src={latestChangelog.previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
className="mt-4 h-auto w-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-base-content/80 mb-2 mt-4 text-left">
|
||||||
|
<MDEditor.Markdown
|
||||||
|
source={latestChangelog.text}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-action">
|
||||||
|
<Button className="btn btn-info btn-outline" onClick={onClose}>
|
||||||
|
Weiter zum HUB
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="modal-backdrop" htmlFor="changelogModalToggle">
|
||||||
|
Close
|
||||||
|
</label>
|
||||||
|
</dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangelogModalBtn = ({
|
||||||
|
latestChangelog,
|
||||||
|
autoOpen,
|
||||||
|
onClose,
|
||||||
|
className = "",
|
||||||
|
hideIcon = false,
|
||||||
|
}: {
|
||||||
|
latestChangelog: Changelog | null | undefined;
|
||||||
|
autoOpen: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
className?: string;
|
||||||
|
hideIcon?: boolean;
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(autoOpen);
|
||||||
|
|
||||||
|
if (!latestChangelog) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
href="#!"
|
||||||
|
className={cn("hover:text-primary flex items-center gap-1", className)}
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
{!hideIcon && <RefreshCw size={12} />} {latestChangelog.title}
|
||||||
|
</a>
|
||||||
|
<ChangelogModal
|
||||||
|
latestChangelog={latestChangelog}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,3 +2,4 @@ export * from "./Badge";
|
|||||||
export * from "./PenaltyDropdown";
|
export * from "./PenaltyDropdown";
|
||||||
export * from "./Maintenance";
|
export * from "./Maintenance";
|
||||||
export * from "./Button";
|
export * from "./Button";
|
||||||
|
export * from "./Changelog";
|
||||||
|
|||||||
@@ -10,8 +10,10 @@
|
|||||||
"@repo/db": "workspace:*",
|
"@repo/db": "workspace:*",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@types/node": "^22.15.29",
|
"@types/node": "^22.15.29",
|
||||||
|
"@uiw/react-md-editor": "^4.0.8",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"lucide-react": "^0.525.0",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -618,12 +618,18 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.15.29
|
specifier: ^22.15.29
|
||||||
version: 22.15.29
|
version: 22.15.29
|
||||||
|
'@uiw/react-md-editor':
|
||||||
|
specifier: ^4.0.8
|
||||||
|
version: 4.0.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
|
lucide-react:
|
||||||
|
specifier: ^0.525.0
|
||||||
|
version: 0.525.0(react@19.1.0)
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
|
|||||||
Reference in New Issue
Block a user