Compare commits

...

3 Commits

Author SHA1 Message Date
800bce807e Merge pull request 'dev' (#4) from dev into production
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 0s
Reviewed-on: #4
2025-03-17 19:55:42 +00:00
2ec0e50b05 test actions
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 0s
2025-03-17 20:55:16 +01:00
9dcb3fd851 fixed some role based logic bugs; fixed ui parts
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 0s
2025-03-17 20:53:45 +01:00
13 changed files with 177 additions and 161 deletions

View File

@ -7,4 +7,5 @@ jobs:
runs-on: linux
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🎉 Test job completed! 🚀"
- run: ssh
- run: echo "🍏 This job's status is ${{ gitea.job_status }}."

View File

@ -2,6 +2,7 @@ import BreadNavigator from "@/components/bread-navigator";
import RenderContent from "@/components/editor/render-content";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { appRoutes } from "@/config";
import { hasPermission, Role } from "@/lib/validation/permissions";
import { auth } from "@/server/auth";
@ -20,8 +21,8 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
? hasPermission(session.user.role, Role.EDITOR)
: false;
return (
<div className="space-y-2">
<div className="flex w-full items-center justify-between">
<div className="">
{/* <div className="flex w-full items-center justify-between">
<div className="flex w-full items-center gap-4">
<BreadNavigator
className="w-full"
@ -39,29 +40,50 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
]}
/>
</div>
{isEditor && (
<div className="flex w-full items-center justify-end space-x-2">
<Badge
className="size-max"
variant={article.published ? "outline" : "destructive"}
>
{article.published ? "Veröffentlicht" : "Draft"}
</Badge>
<Button asChild variant={"outline"}>
<Link href={appRoutes.editArticle(article.slug)}>
<Edit className="size-4" />
<span>Bearbeiten</span>
</Link>
</Button>
<Button size={"icon"} variant={"outline"}>
<MoreVertical className="size-4" />
</Button>
</div> */}
<div className="flex w-full flex-col gap-4 xl:flex-row">
<div className="w-full space-y-4">
<h1 className="text-4xl font-bold">{article.title}</h1>
<div className="w-full">
{article.content && <RenderContent content={article.content} />}
</div>
)}
</div>
<Separator
orientation="vertical"
className="sticky top-0 hidden h-screen -translate-y-4 xl:block"
/>
<div
className={
"top-4 h-max w-full max-w-xl space-y-4 xl:sticky xl:max-w-sm"
}
>
<div className="relative min-h-96 w-full space-y-4 overflow-hidden rounded-md border bg-background p-4">
<p className="text-center text-muted-foreground">
Kommentare bald verfügbar
</p>
</div>
{isEditor && (
<div className="flex w-full items-center justify-end space-x-2">
<Badge
className="size-max"
variant={article.published ? "outline" : "destructive"}
>
{article.published ? "Veröffentlicht" : "Draft"}
</Badge>
<Button asChild variant={"outline"}>
<Link href={appRoutes.editArticle(article.slug)}>
<Edit className="size-4" />
<span>Bearbeiten</span>
</Link>
</Button>
<Button size={"icon"} variant={"outline"}>
<MoreVertical className="size-4" />
</Button>
</div>
)}
</div>
</div>
<h1 className="text-4xl font-bold">{article.title}</h1>
{article.content && <RenderContent content={article.content} />}
</div>
);
}

View File

@ -24,6 +24,7 @@ async function Page({ params }: CategoryPageProps) {
articles: true,
},
});
console.log(category);
if (!category) return notFound();

View File

@ -23,6 +23,7 @@ function UserForm({ server_user, cb }: { server_user: User; cb?: () => void }) {
const form = useForm<z.infer<typeof userSchema>>({
resolver: zodResolver(userSchema),
defaultValues: {
email: server_user.email ?? "",
name: server_user?.name ?? "",
},
});

View File

@ -15,7 +15,7 @@ export default async function Home() {
return (
<>
<Alert>
<div className="flex gap-2">
<div className="flex flex-col gap-2 sm:flex-row">
<Icons.logo className="size-8" />
<div>
<AlertTitle className="font-bold">
@ -25,7 +25,7 @@ export default async function Home() {
Lorem ipsum, dolor sit amet consectetur adipisicing elit.
</AlertDescription>
</div>
<Button asChild className="ml-auto">
<Button asChild className="sm:ml-auto">
<Link href={appRoutes.about}>
Erfahre mehr über {appConfig.name}
</Link>

View File

@ -68,7 +68,7 @@ export default ({ server_article }: { server_article: Article }) => {
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col-reverse gap-4 space-y-4 xl:flex-row"
className="flex w-full flex-col-reverse gap-4 xl:flex-row"
>
<div className="flex w-full max-w-3xl flex-col gap-2">
<FormField
@ -112,12 +112,7 @@ export default ({ server_article }: { server_article: Article }) => {
/>
</div>
<div
className={cn(
"top-4 h-max w-full max-w-xl xl:sticky xl:max-w-md",
// loading && "border-t-blue-600",
)}
>
<div className={"top-4 h-max w-full max-w-xl xl:sticky xl:max-w-md"}>
<div className="relative w-full space-y-4 overflow-hidden rounded-md border bg-background p-4">
<div
className={cn(

View File

@ -6,18 +6,31 @@ import { Icons } from "../icons";
function CategorySelect(props: Partial<ComboboxProps>) {
const { data: categories } = api.category.getMany.useQuery();
const initialValue = categories?.find(
(c) => c.id === props?.initialValue,
)?.slug;
return (
<Combobox
{...(props as ComboboxProps)}
initialValue={initialValue}
messageUi={{
select: "Kategorie auswählen...",
selectIcon: Icons.category,
placeholder: "Kategorie suchen...",
empty: "Keine Kategorien gefunden",
}}
onSelect={(value) => {
const id = categories?.find((c) => {
return c.slug === value;
})?.id!;
props?.onSelect?.(id);
}}
data={
categories?.map(({ name, slug }) => ({ label: name, value: slug })) ??
[]
categories?.map(({ name, slug }) => ({
label: name,
value: slug,
})) ?? []
}
/>
);

View File

@ -58,22 +58,18 @@ const updatedImage = UpdatedImage.configure({
},
});
const taskList = TaskList.configure({
HTMLAttributes: {
class: cx("not-prose pl-2 "),
},
});
const taskList = TaskList.configure({});
const taskItem = TaskItem.configure({
HTMLAttributes: {
class: cx("flex gap-2 items-start my-4"),
class: cx("flex gap-2 items-start "),
},
nested: true,
});
const horizontalRule = HorizontalRule.configure({
HTMLAttributes: {
class: cx("mt-4 mb-6 border-t border-muted-foreground"),
class: cx("mt-4 mb-6 border border-border"),
},
});
@ -85,6 +81,21 @@ const starterKit = StarterKit.configure({
heading: {
levels: [2, 3, 4, 5, 6],
},
bulletList: {
HTMLAttributes: {
class: cx("list-disc ml-4 px-2 "),
},
},
orderedList: {
HTMLAttributes: {
class: cx("list-decimal ml-4 px-2 "),
},
},
blockquote: {
HTMLAttributes: {
class: cx("m-4 border-l-4 border-border pl-4"),
},
},
gapcursor: false,
horizontalRule: false,
});

View File

@ -17,18 +17,38 @@
margin: 0.75rem 0;
}
.tiptap ul,
.tiptap ol {
margin-left: 1rem;
padding: 0.25rem;
ul[data-type="taskList"] li > label input[type="checkbox"] {
@apply size-4 border border-border bg-muted;
-webkit-appearance: none;
appearance: none;
margin: 0;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
cursor: pointer;
position: relative;
transform: translateY(0.17rem);
display: grid;
border-radius: 0.25rem;
place-content: center;
}
.tiptap ul {
list-style-type: disc;
ul[data-type="taskList"] li > label input[type="checkbox"]::before {
content: "";
width: 0.65em;
height: 0.65em;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em;
transform-origin: center;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
.tiptap ol {
list-style-type: decimal;
ul[data-type="taskList"] li > label input[type="checkbox"]:checked::before {
transform: scale(1);
}
ul[data-type="taskList"] li[data-checked="true"] > div > p {
@apply text-foreground/75;
text-decoration: line-through;
text-decoration-thickness: 2px;
}
/* Heading styles */
@ -65,17 +85,9 @@
font-size: 1.125rem; /* Equivalent to text-lg */
}
.tiptap blockquote {
@apply m-4 border-l-4 border-border pl-4;
/* border-left: 1rem solid var(--primary);
padding-left: 1rem; */
}
.tiptap hr {
@apply border-border;
/* border-color: var(--border); */
margin: 0.25rem 0;
}
/* .tiptap blockquote {
@apply ;
} */
::selection {
background-color: #5abbf7;
@ -89,52 +101,6 @@ img {
@apply rounded-md;
}
/* Task List */
ul[data-type="taskList"] li {
@apply m-0 my-3 flex items-center;
}
ul[data-type="taskList"] li p {
@apply m-0 my-0 flex items-center;
}
ul[data-type="taskList"] li > label input[type="checkbox"] {
@apply size-4 border border-border bg-muted;
-webkit-appearance: none;
appearance: none;
margin: 0;
cursor: pointer;
position: relative;
display: grid;
border-radius: 0.25rem;
place-content: center;
}
/* ul[data-type="taskList"] li > label input[type="checkbox"]:hover {
background-color: #000;
}
ul[data-type="taskList"] li > label input[type="checkbox"]:active {
background-color: #000;
} */
ul[data-type="taskList"] li > label input[type="checkbox"]::before {
content: "";
width: 0.65em;
height: 0.65em;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em;
transform-origin: center;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
ul[data-type="taskList"] li > label input[type="checkbox"]:checked::before {
transform: scale(1);
}
ul[data-type="taskList"] li[data-checked="true"] > div > p {
@apply text-foreground/75;
text-decoration: line-through;
text-decoration-thickness: 2px;
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
outline: none !important;
background-color: var(--novel-highlight-blue);

View File

@ -4,7 +4,7 @@ import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
import { usersRouter } from "./routers/users";
import { authorRouter } from "./routers/author";
import { appRouter as globalRouter } from "./routers/app";
import { authRouter } from "./routers/auth";
// import { authRouter } from "./routers/auth";
/**
* This is the primary router for your server.
*
@ -16,7 +16,7 @@ export const appRouter = createTRPCRouter({
users: usersRouter,
author: authorRouter,
app: globalRouter,
auth: authRouter,
// auth: authRouter,
});
// export type definition of API

View File

@ -162,10 +162,15 @@ export const articleRouter = createTRPCRouter({
)
.query(async ({ ctx, input }) => {
const limit = input?.limit ?? 50;
return (await ctx.db.query.articles.findMany({
where: input?.categoryId
const where = and(
input?.categoryId
? eq(articles.categoryId, input.categoryId)
: undefined,
eq(articles.published, true),
);
return (await ctx.db.query.articles.findMany({
where,
limit: limit,
columns: {
title: true,

View File

@ -1,54 +1,54 @@
import { passwordSchema, userSchema } from "@/lib/validation/zod/user";
import { createTRPCRouter, publicProcedure } from "../trpc";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { users } from "@/server/db/schema";
import argon from "argon2";
// import { passwordSchema, userSchema } from "@/lib/validation/zod/user";
// import { createTRPCRouter, publicProcedure } from "../trpc";
// import { z } from "zod";
// import { eq } from "drizzle-orm";
// import { users } from "@/server/db/schema";
// import argon from "argon2";
export const authRouter = createTRPCRouter({
register: publicProcedure
.input(
z.object({
user: userSchema,
password: passwordSchema,
}),
)
.mutation(async ({ ctx, input }) => {
const {
password,
user: { email, name },
} = input;
// Check if user already exists
try {
const existingUser = await ctx.db.query.users.findFirst({
where: eq(users.email, email),
});
// export const authRouter = createTRPCRouter({
// register: publicProcedure
// .input(
// z.object({
// user: userSchema,
// password: passwordSchema,
// }),
// )
// .mutation(async ({ ctx, input }) => {
// const {
// password,
// user: { email, name },
// } = input;
// // Check if user already exists
// try {
// const existingUser = await ctx.db.query.users.findFirst({
// where: eq(users.email, email),
// });
if (existingUser) {
return { success: false, message: "User already exists" };
}
// if (existingUser) {
// return { success: false, message: "User already exists" };
// }
// Hash the password (12 is a good cost factor)
const hashedPassword = await argon.hash(password);
// // Hash the password (12 is a good cost factor)
// const hashedPassword = await argon.hash(password);
// Create user in database
const [user] = await ctx.db
.insert(users)
.values({
name,
email,
password: hashedPassword,
})
.returning({ id: users.id });
console.log(user);
// // Create user in database
// const [user] = await ctx.db
// .insert(users)
// .values({
// name,
// email,
// password: hashedPassword,
// })
// .returning({ id: users.id });
// console.log(user);
if (user) {
return { success: true, message: "User created successfully" };
}
return { success: false, message: "Error creating user" };
} catch (e) {
console.error(e);
return { success: false, message: "Error creating user" };
}
}),
});
// if (user) {
// return { success: true, message: "User created successfully" };
// }
// return { success: false, message: "Error creating user" };
// } catch (e) {
// console.error(e);
// return { success: false, message: "Error creating user" };
// }
// }),
// });

View File

@ -85,6 +85,7 @@ export const categoryRouter = createTRPCRouter({
name: true,
slug: true,
createdAt: true,
id: true,
},
})) as Category[];
}),