Small changes to label form
This commit is contained in:
parent
b103b5418b
commit
0802e36796
@ -0,0 +1,3 @@
|
|||||||
|
2024-11-18T16:54:25.722Z error: Authentication error. User: "ryan@pandu.ski", Message: "no such table: user", IP-Address: "::ffff:127.0.0.1"
|
||||||
|
2024-11-18T16:54:31.817Z error: Authentication error. User: "ryan@ryanpandya.com", Message: "no such table: user", IP-Address: "::ffff:127.0.0.1"
|
||||||
|
2024-11-18T16:54:43.805Z error: Authentication error. User: "ryan@ryanpandya.com", Message: "no such table: user", IP-Address: "::ffff:127.0.0.1"
|
||||||
@ -34,6 +34,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { zLabelSchema } from "@lifetracker/shared/types/labels";
|
import { zLabelSchema } from "@lifetracker/shared/types/labels";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
type CreateLabelSchema = z.infer<typeof zLabelSchema>;
|
type CreateLabelSchema = z.infer<typeof zLabelSchema>;
|
||||||
|
|
||||||
@ -46,13 +47,6 @@ export default function AddLabelDialog({
|
|||||||
const [isOpen, onOpenChange] = useState(false);
|
const [isOpen, onOpenChange] = useState(false);
|
||||||
const form = useForm<CreateLabelSchema>({
|
const form = useForm<CreateLabelSchema>({
|
||||||
resolver: zodResolver(zLabelSchema),
|
resolver: zodResolver(zLabelSchema),
|
||||||
defaultValues: {
|
|
||||||
id: "69",
|
|
||||||
name: "Fuckdicks",
|
|
||||||
code: 420,
|
|
||||||
description: "This shit sucks",
|
|
||||||
color: "#004400",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const { mutate, isPending } = api.labels.createLabel.useMutation({
|
const { mutate, isPending } = api.labels.createLabel.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -87,17 +81,36 @@ export default function AddLabelDialog({
|
|||||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add User</DialogTitle>
|
<DialogTitle>Create Label</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit((val) => mutate(val))}>
|
<form onSubmit={form.handleSubmit((val) => mutate(val))}>
|
||||||
<div className="flex w-full flex-col space-y-2">
|
<div className="flex w-full flex-col space-y-2">
|
||||||
|
<div style={{ display: "grid", gridTemplateColumns: "5em 1fr 75px", gap: "10px" }}>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="code"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
{/* <FormLabel>Code</FormLabel> */}
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Code"
|
||||||
|
{...field}
|
||||||
|
className="w-full rounded border p-2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
{/* <FormLabel>Name</FormLabel> */}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@ -112,14 +125,14 @@ export default function AddLabelDialog({
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="code"
|
name="color"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Code</FormLabel>
|
{/* <FormLabel>Color</FormLabel> */}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="text"
|
||||||
placeholder="Code"
|
placeholder="Color"
|
||||||
{...field}
|
{...field}
|
||||||
className="w-full rounded border p-2"
|
className="w-full rounded border p-2"
|
||||||
/>
|
/>
|
||||||
@ -128,15 +141,15 @@ export default function AddLabelDialog({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="description"
|
name="description"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
{/* <FormLabel>Description</FormLabel> */}
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Textarea
|
||||||
type="text"
|
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
{...field}
|
{...field}
|
||||||
className="w-full rounded border p-2"
|
className="w-full rounded border p-2"
|
||||||
@ -146,24 +159,6 @@ export default function AddLabelDialog({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="color"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Color, hope you like hex codes</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Color"
|
|
||||||
{...field}
|
|
||||||
className="w-full rounded border p-2"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DialogFooter className="sm:justify-end">
|
<DialogFooter className="sm:justify-end">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="button" variant="secondary">
|
<Button type="button" variant="secondary">
|
||||||
|
|||||||
@ -3,26 +3,26 @@ import { ActionButton } from "@/components/ui/action-button";
|
|||||||
import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
|
import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
import { useDeleteTag } from "@hoarder/shared-react/hooks/tags";
|
import { useDeleteLabel } from "@lifetracker/shared-react/hooks/labels";
|
||||||
|
|
||||||
export default function DeleteTagConfirmationDialog({
|
export default function DeleteLabelConfirmationDialog({
|
||||||
tag,
|
label,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
}: {
|
}: {
|
||||||
tag: { id: string; name: string };
|
label: { id: string; code: number; name: string };
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
setOpen?: (v: boolean) => void;
|
setOpen?: (v: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const currentPath = usePathname();
|
const currentPath = usePathname();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutate: deleteTag, isPending } = useDeleteTag({
|
const { mutate: deleteTag, isPending } = useDeleteLabel({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({
|
toast({
|
||||||
description: `Tag "${tag.name}" has been deleted!`,
|
description: `Label "${label.name}" has been deleted!`,
|
||||||
});
|
});
|
||||||
if (currentPath.includes(tag.id)) {
|
if (currentPath.includes(label.code)) {
|
||||||
router.push("/dashboard/tags");
|
router.push("/dashboard/labels");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@ -36,8 +36,8 @@ export default function DeleteTagConfirmationDialog({
|
|||||||
<ActionConfirmingDialog
|
<ActionConfirmingDialog
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
title={`Delete ${tag.name}?`}
|
title={`Delete ${label.name}?`}
|
||||||
description={`Are you sure you want to delete the tag "${tag.name}"?`}
|
description={`Are you sure you want to delete the label "${label.name}"?`}
|
||||||
actionButton={(setDialogOpen) => (
|
actionButton={(setDialogOpen) => (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
type="button"
|
type="button"
|
||||||
@ -45,7 +45,7 @@ export default function DeleteTagConfirmationDialog({
|
|||||||
loading={isPending}
|
loading={isPending}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
deleteTag(
|
deleteTag(
|
||||||
{ tagId: tag.id },
|
{ labelId: label.id },
|
||||||
{ onSuccess: () => setDialogOpen(false) },
|
{ onSuccess: () => setDialogOpen(false) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -17,12 +17,30 @@ import { Check, KeyRound, Pencil, Trash, FilePlus, X } from "lucide-react";
|
|||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import AddLabelDialog from "./AddLabelDialog";
|
import AddLabelDialog from "./AddLabelDialog";
|
||||||
import EditLabelDialog from "./EditLabelDialog";
|
import EditLabelDialog from "./EditLabelDialog";
|
||||||
|
import DeleteLabelConfirmationDialog from "./DeleteLabelConfirmationDialog";
|
||||||
|
|
||||||
export default function LabelsView() {
|
export default function LabelsView() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const { data: labels } = api.labels.list.useQuery();
|
const { data: labels } = api.labels.list.useQuery();
|
||||||
const { data: labelStats } = api.labels.labelStats.useQuery();
|
const { data: labelStats } = api.labels.labelStats.useQuery();
|
||||||
|
|
||||||
|
const invalidateLabelList = api.useUtils().labels.list.invalidate;
|
||||||
|
const { mutate: deleteLabel, isPending: isDeletionPending } =
|
||||||
|
api.labels.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
description: "Label deleted",
|
||||||
|
});
|
||||||
|
invalidateLabelList();
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
description: `Something went wrong: ${e.message}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const LabelsTable = ({ labels }) => (
|
const LabelsTable = ({ labels }) => (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-gray-200">
|
<TableHeader className="bg-gray-200">
|
||||||
|
|||||||
@ -7,17 +7,19 @@ import path from "path";
|
|||||||
|
|
||||||
import dbConfig from "./drizzle.config";
|
import dbConfig from "./drizzle.config";
|
||||||
|
|
||||||
|
console.log("dbURL: ", dbConfig.dbCredentials.url);
|
||||||
|
|
||||||
const sqlite = new Database(dbConfig.dbCredentials.url);
|
const sqlite = new Database(dbConfig.dbCredentials.url);
|
||||||
export const db = drizzle(sqlite, {
|
export const db = drizzle(sqlite, {
|
||||||
schema,
|
schema,
|
||||||
// logger: true
|
logger: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getInMemoryDB(runMigrations: boolean) {
|
export function getInMemoryDB(runMigrations: boolean) {
|
||||||
const mem = new Database(":memory:");
|
const mem = new Database(":memory:");
|
||||||
const db = drizzle(mem, { schema, logger: true });
|
const db = drizzle(mem, { schema, logger: true });
|
||||||
if (runMigrations) {
|
if (runMigrations) {
|
||||||
migrate(db, { migrationsFolder: path.resolve(__dirname, "./drizzle") });
|
migrate(db, { migrationsFolder: path.resolve(__dirname, "./migrations") });
|
||||||
}
|
}
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
50
packages/shared-react/hooks/labels.ts
Normal file
50
packages/shared-react/hooks/labels.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { api } from "../trpc";
|
||||||
|
|
||||||
|
export function useUpdateLabel(
|
||||||
|
...opts: Parameters<typeof api.labels.update.useMutation>
|
||||||
|
) {
|
||||||
|
const apiUtils = api.useUtils();
|
||||||
|
|
||||||
|
return api.tags.update.useMutation({
|
||||||
|
...opts[0],
|
||||||
|
onSuccess: (res, req, meta) => {
|
||||||
|
apiUtils.labels.list.invalidate();
|
||||||
|
apiUtils.labels.get.invalidate({ labelId: res.id });
|
||||||
|
// apiUtils.bookmarks.getBookmarks.invalidate({
|
||||||
|
// labelId: res.id;
|
||||||
|
|
||||||
|
// TODO: Maybe we can only look at the cache and invalidate only affected bookmarks
|
||||||
|
// apiUtils.bookmarks.getBookmark.invalidate();
|
||||||
|
return opts[0]?.onSuccess?.(res, req, meta);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteLabel(
|
||||||
|
...opts: Parameters<typeof api.labels.delete.useMutation>
|
||||||
|
) {
|
||||||
|
const apiUtils = api.useUtils();
|
||||||
|
|
||||||
|
return api.labels.delete.useMutation({
|
||||||
|
...opts[0],
|
||||||
|
onSuccess: (res, req, meta) => {
|
||||||
|
apiUtils.labels.list.invalidate();
|
||||||
|
// apiUtils.bookmarks.getBookmark.invalidate();
|
||||||
|
return opts[0]?.onSuccess?.(res, req, meta);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteUnusedTags(
|
||||||
|
...opts: Parameters<typeof api.tags.deleteUnused.useMutation>
|
||||||
|
) {
|
||||||
|
const apiUtils = api.useUtils();
|
||||||
|
|
||||||
|
return api.tags.deleteUnused.useMutation({
|
||||||
|
...opts[0],
|
||||||
|
onSuccess: (res, req, meta) => {
|
||||||
|
apiUtils.tags.list.invalidate();
|
||||||
|
return opts[0]?.onSuccess?.(res, req, meta);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import { api } from "../trpc";
|
|
||||||
|
|
||||||
export function useUpdateTag(
|
|
||||||
...opts: Parameters<typeof api.tags.update.useMutation>
|
|
||||||
) {
|
|
||||||
const apiUtils = api.useUtils();
|
|
||||||
|
|
||||||
return api.tags.update.useMutation({
|
|
||||||
...opts[0],
|
|
||||||
onSuccess: (res, req, meta) => {
|
|
||||||
apiUtils.tags.list.invalidate();
|
|
||||||
apiUtils.tags.get.invalidate({ tagId: res.id });
|
|
||||||
apiUtils.bookmarks.getBookmarks.invalidate({ tagId: res.id });
|
|
||||||
|
|
||||||
// TODO: Maybe we can only look at the cache and invalidate only affected bookmarks
|
|
||||||
apiUtils.bookmarks.getBookmark.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMergeTag(
|
|
||||||
...opts: Parameters<typeof api.tags.merge.useMutation>
|
|
||||||
) {
|
|
||||||
const apiUtils = api.useUtils();
|
|
||||||
|
|
||||||
return api.tags.merge.useMutation({
|
|
||||||
...opts[0],
|
|
||||||
onSuccess: (res, req, meta) => {
|
|
||||||
apiUtils.tags.list.invalidate();
|
|
||||||
[res.mergedIntoTagId, ...res.deletedTags].forEach((tagId) => {
|
|
||||||
apiUtils.tags.get.invalidate({ tagId });
|
|
||||||
apiUtils.bookmarks.getBookmarks.invalidate({ tagId });
|
|
||||||
});
|
|
||||||
// TODO: Maybe we can only look at the cache and invalidate only affected bookmarks
|
|
||||||
apiUtils.bookmarks.getBookmark.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteTag(
|
|
||||||
...opts: Parameters<typeof api.tags.delete.useMutation>
|
|
||||||
) {
|
|
||||||
const apiUtils = api.useUtils();
|
|
||||||
|
|
||||||
return api.tags.delete.useMutation({
|
|
||||||
...opts[0],
|
|
||||||
onSuccess: (res, req, meta) => {
|
|
||||||
apiUtils.tags.list.invalidate();
|
|
||||||
apiUtils.bookmarks.getBookmark.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteUnusedTags(
|
|
||||||
...opts: Parameters<typeof api.tags.deleteUnused.useMutation>
|
|
||||||
) {
|
|
||||||
const apiUtils = api.useUtils();
|
|
||||||
|
|
||||||
return api.tags.deleteUnused.useMutation({
|
|
||||||
...opts[0],
|
|
||||||
onSuccess: (res, req, meta) => {
|
|
||||||
apiUtils.tags.list.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -185,9 +185,6 @@ export const labelsAppRouter = router({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
console.log(input);
|
|
||||||
console.log(ctx);
|
|
||||||
console.log("TEEEEEEEEEEEEEEEEEEEEEST\n\n\n\n\n");
|
|
||||||
try {
|
try {
|
||||||
const res = await ctx.db
|
const res = await ctx.db
|
||||||
.update(bookmarkTags)
|
.update(bookmarkTags)
|
||||||
@ -238,4 +235,31 @@ export const labelsAppRouter = router({
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
delete: authedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
labelId: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
// const affectedBookmarks = await ctx.db
|
||||||
|
// .select({
|
||||||
|
// bookmarkId: tagsOnBookmarks.bookmarkId,
|
||||||
|
// })
|
||||||
|
// .from(tagsOnBookmarks)
|
||||||
|
// .where(
|
||||||
|
// and(
|
||||||
|
// eq(tagsOnBookmarks.tagId, input.tagId),
|
||||||
|
// // Tag ownership is checked in the ensureTagOwnership middleware
|
||||||
|
// // eq(bookmarkTags.userId, ctx.user.id),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
const res = await ctx.db
|
||||||
|
.delete(labels)
|
||||||
|
.where(conditionFromInput(input, ctx.user.id));
|
||||||
|
if (res.changes == 0) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND" });
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue
Block a user