This commit is contained in:
2025-03-06 20:50:31 +01:00
commit 4a1397651f
68 changed files with 10049 additions and 0 deletions
@@ -0,0 +1,81 @@
"use client";
import React from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { articleSchema } from "@/lib/validation/zod/article";
import { createArticle } from "@/server/actions/article";
const formSchema = articleSchema.pick({
title: true,
});
function CreateArticleDialog() {
const [open, setOpen] = React.useState<boolean>(false);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
},
});
// 2. Define a submit handler.
async function onSubmit(values: z.infer<typeof formSchema>) {
setOpen(false);
await createArticle(values);
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<Button asChild>
<DialogTrigger>Artikel Erstellen</DialogTrigger>
</Button>
<DialogContent>
<DialogHeader>
<DialogTitle>Neuen Artikel Erstellen</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Titel</FormLabel>
<FormControl>
<Input placeholder="Artikel Titel" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Artikel Erstellen</Button>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
export default CreateArticleDialog;
@@ -0,0 +1,19 @@
import { hasPermission, Role } from "@/lib/validation/has-permission";
import { auth } from "@/server/auth";
import { api } from "@/trpc/server";
import { notFound } from "next/navigation";
import React from "react";
import Editor from "@/components/article/editor/article-form";
async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const session = await auth();
const isEditor = session?.user
? hasPermission(session.user.role, Role.EDITOR)
: false;
const article = await api.article.get({ slug: slug });
if (!article || !isEditor) return notFound();
return <Editor server_article={article} />;
}
export default Page;
+40
View File
@@ -0,0 +1,40 @@
import { Button } from "@/components/ui/button";
import { hasPermission, Role } from "@/lib/validation/has-permission";
import { auth } from "@/server/auth";
import { api } from "@/trpc/server";
import { Edit, Edit2Icon, Edit3, MoreVertical, Trash } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
import React from "react";
async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const article = await api.article.get({ slug: slug });
if (!article) return notFound();
const session = await auth();
const isEditor = session?.user
? hasPermission(session.user.role, Role.EDITOR)
: false;
return (
<div>
<div className="flex w-full items-center justify-between">
<h1 className="text-4xl font-bold">{article.title}</h1>
{isEditor && (
<div className="space-x-2">
<Button asChild variant={"outline"}>
<Link href={`/artikel/${article.slug}/edit`}>
<Edit className="size-4" />
<span>Bearbeiten</span>
</Link>
</Button>
<Button size={"icon"} variant={"outline"}>
<MoreVertical className="size-4" />
</Button>
</div>
)}
</div>
</div>
);
}
export default Page;
+9
View File
@@ -0,0 +1,9 @@
import React from 'react'
function Page() {
return (
<div>Alle Artikel Page</div>
)
}
export default Page
@@ -0,0 +1,8 @@
import React from "react";
async function Page({ params }: { params: Promise<{ name: string }> }) {
const { name } = await params;
return <div>Kategorie {name}</div>;
}
export default Page;
+7
View File
@@ -0,0 +1,7 @@
import React from "react";
function Page() {
return <div>Alle Kategorien</div>;
}
export default Page;
+18
View File
@@ -0,0 +1,18 @@
import { AppSidebar } from "@/components/layout/app-sidebar";
import Navbar from "@/components/layout/navbar";
import { SidebarProvider } from "@/components/ui/sidebar";
import React from "react";
function Layout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<div className="w-full">
<Navbar />
<main className="p-4">{children}</main>
</div>
</SidebarProvider>
);
}
export default Layout;
+21
View File
@@ -0,0 +1,21 @@
import { auth } from "@/server/auth";
import { api } from "@/trpc/server";
import Link from "next/link";
export default async function Home() {
const session = await auth();
const articles = await api.article.getAllPreviews();
return (
<div className="">
<h1 className="text-4xl font-bold">Anti Rechts Wiki</h1>
<menu>
{articles.map((article) => (
<li key={article.slug}>
<Link href={`/artikel/${article.slug}`}>{article.title}</Link>
</li>
))}
</menu>
</div>
);
}
+3
View File
@@ -0,0 +1,3 @@
import { handlers } from "@/server/auth";
export const { GET, POST } = handlers;
+34
View File
@@ -0,0 +1,34 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";
import { env } from "@/env";
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";
/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a HTTP request (e.g. when you make requests from Client Components).
*/
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};
const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
export { handler as GET, handler as POST };
+24
View File
@@ -0,0 +1,24 @@
import "@/styles/globals.css";
import { GeistSans } from "geist/font/sans";
import { type Metadata } from "next";
import { TRPCReactProvider } from "@/trpc/react";
export const metadata: Metadata = {
title: "Create T3 App",
description: "Generated by create-t3-app",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${GeistSans.variable}`}>
<body>
<TRPCReactProvider>{children}</TRPCReactProvider>
</body>
</html>
);
}