Skip to content

Commit

Permalink
Implement Tag Creation
Browse files Browse the repository at this point in the history
Added button to no result section in tags.
Create tag Modal is created and being used.
CreateTag endpoint is added to tag router.
  • Loading branch information
bekogeko committed Nov 29, 2023
1 parent ff160dd commit 07823b3
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 8 deletions.
7 changes: 4 additions & 3 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ model Comment {
}

model Tag {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
slug String @unique
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
slug String @unique

articles TagsOnPosts[]
}

Expand Down
85 changes: 85 additions & 0 deletions src/components/Dialogs/CreateTagModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use client";

import { Dialog } from "@headlessui/react";
import { api } from "~/utils/api";
import { useState, useRef } from "react";

interface CreateTagModalProps {
isOpen: boolean;
onClose: () => void;
}

const CreateTagModal = ({ isOpen, onClose }: CreateTagModalProps) => {
const createTagMutation = api.tag.createTag.useMutation();

const tagNameInputRef = useRef<HTMLInputElement>(null);

const [tagName, setTagName] = useState("");

return (
<Dialog
open={isOpen}
onClose={onClose}
className="fixed inset-0 z-10 flex items-center justify-center bg-black bg-opacity-30"
>
<Dialog.Panel className="group w-full max-w-md rounded-md bg-white p-4">
<Dialog.Title className="text-xl font-bold">Create Tag</Dialog.Title>
<Dialog.Description className="text-base">
Create a new tag for this post.
</Dialog.Description>
<div className="mt-4">
<label htmlFor="tag_name" className="">
Shown Name
</label>
<input
type="text"
placeholder="Tag Name"
className=" w-full rounded border-2 p-2 invalid:border-red-500 invalid:outline-red-500"
name="tag_name"
// accept only alpha-numeric characters
pattern="(\w*)( ?(\w+))*"
// pattern="(\w*)(\W)*(( )*([A-Z]+\w*))*"
value={tagName}
onChange={(e) => setTagName(e.target.value)}
ref={tagNameInputRef}
/>
</div>
<div className="mt-4 flex items-center">
<p>/tags/</p>
<input
type="text"
value={tagName
.replaceAll(/[^a-zA-z ]/g, "")
.toLowerCase()
.replaceAll(/ +/g, "-")}
className="inline-block flex-shrink flex-grow rounded border p-1"
disabled={true}
/>
<p>/</p>
</div>

<div className="mt-2 flex justify-end">
<button
className="rounded bg-indigo-500 p-2 text-white disabled:bg-indigo-200"
onClick={() => {
createTagMutation.mutate({
name: tagName,
slug: tagName
.replaceAll(/[^a-zA-z ]/g, "")
.toLowerCase()
.replaceAll(/ +/g, "-"),
});
onClose();
}}
disabled={tagNameInputRef.current?.checkValidity() === false}
>
{createTagMutation.isLoading ? "Creating..." : "Create"}
&nbsp;Tag
</button>
</div>
</Dialog.Panel>
</Dialog>
);
};

export default CreateTagModal;
27 changes: 24 additions & 3 deletions src/components/Tabs/TagTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { api } from "~/utils/api";
import Loading from "../Loading";
import CreateTagModal from "../Dialogs/CreateTagModal";

const TagTab = () => {
// get slug from url using useRouters
Expand All @@ -12,7 +13,6 @@ const TagTab = () => {

const {
data: tagData,

isLoading: isTagDataLoading,
refetch: refetchTagData,
} = api.tag.getTagsBySlug.useQuery({
Expand All @@ -28,6 +28,7 @@ const TagTab = () => {

const [isInTimeout, setIsInTimeout] = useState(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
const [createTagIsOpen, setCreateTagIsOpen] = useState(false);

const changeQuery = (newQuery: string) => {
// check if there is a timeout
Expand Down Expand Up @@ -63,8 +64,10 @@ const TagTab = () => {
if (addTagMutation.isSuccess) {
setQuerySelectionTag("");
refetchTagData();
} else {
// TODO: Add Toast for error
}
}, [refetchTagData, addTagMutation.isSuccess]);
}, [refetchTagData, addTagMutation.isSuccess, addTagMutation.error]);

useEffect(() => {
if (removeTagMutation.isSuccess) {
Expand Down Expand Up @@ -131,7 +134,18 @@ const TagTab = () => {
{isSearchLoading ? (
<Loading className="h-6 w-6 border-2" />
) : !filteredTagOptions || filteredTagOptions.length === 0 ? (
<div className="p-2">No results found</div>
<div className="flex items-center p-2">
<p> No results found.</p>
<button
className="ml-2 rounded bg-indigo-500 p-2 text-white"
onClick={() => {
// open create tag modal
setCreateTagIsOpen(true);
}}
>
Create Tag
</button>
</div>
) : (
filteredTagOptions?.map((tag) => (
<Combobox.Option
Expand Down Expand Up @@ -170,6 +184,13 @@ const TagTab = () => {
{addTagMutation.isLoading && <Loading className="h-6 w-6 border-2" />}
Add Tag
</button>

<CreateTagModal
isOpen={createTagIsOpen}
onClose={() => {
setCreateTagIsOpen(false);
}}
/>
</div>
</div>
);
Expand Down
24 changes: 22 additions & 2 deletions src/server/api/router/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ export const tagRouter = createTRPCRouter({
},
});

// if the tag is already assigned to the article, return the tag
if (tagsOnPosts) return tagsOnPosts;
// if the tag is already assigned to the article, return an error
if (tagsOnPosts)
throw new TRPCError({
code: "BAD_REQUEST",
message: "Tag is already assigned to the article",
});

// if the tag is not assigned to the article, create the tag
const newTag = await prisma.tagsOnPosts.create({
Expand Down Expand Up @@ -263,4 +267,20 @@ export const tagRouter = createTRPCRouter({

return articlesInTag;
}),

createTag: protectedProcedure
.input(z.object({ name: z.string(), slug: z.string() }))
.mutation(async ({ input, ctx }) => {
const { prisma } = ctx;

// create the tag
const tag = await prisma.tag.create({
data: {
name: input.name,
slug: input.slug,
},
});

return tag;
}),
});

0 comments on commit 07823b3

Please sign in to comment.