From f896b9715a4e2e24613fb6264015bdf202abda84 Mon Sep 17 00:00:00 2001
From: Jordi Enric
Date: Sun, 12 May 2024 14:33:18 +0200
Subject: [PATCH] fix tags crud
---
.../src/components/Editor/Editor.queries.ts | 2 +-
.../src/components/Tags/CreateTagDialog.tsx | 18 +-
.../src/components/Tags/UpdateTagDialog.tsx | 62 +++--
apps/web/src/pages/blogs/[blogId]/posts.tsx | 60 +++--
apps/web/src/queries/tags.ts | 18 +-
apps/web/src/types/supabase.ts | 214 ++++++++++++++++--
package.json | 1 +
7 files changed, 298 insertions(+), 77 deletions(-)
diff --git a/apps/web/src/components/Editor/Editor.queries.ts b/apps/web/src/components/Editor/Editor.queries.ts
index 0cf2742..b3e48c2 100644
--- a/apps/web/src/components/Editor/Editor.queries.ts
+++ b/apps/web/src/components/Editor/Editor.queries.ts
@@ -44,7 +44,7 @@ export const useCreateBlogTag = () => {
},
{
onSuccess: () => {
- queryClient.invalidateQueries(["tags"]);
+ queryClient.invalidateQueries(["blog-tags"]);
},
}
);
diff --git a/apps/web/src/components/Tags/CreateTagDialog.tsx b/apps/web/src/components/Tags/CreateTagDialog.tsx
index d54b630..25d55d7 100644
--- a/apps/web/src/components/Tags/CreateTagDialog.tsx
+++ b/apps/web/src/components/Tags/CreateTagDialog.tsx
@@ -37,19 +37,19 @@ export function CreateTagDialog({ blogId }: PropsWithChildren) {
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
- const target = e.target as HTMLFormElement;
- const formData = new FormData(target);
- const name = formData.get("name") as string;
- const slug = formData.get("slug") as string;
- if (!name || !slug) {
+ if (!title || !slug) {
toast.error("Name and slug are required");
return;
}
- await createTag.mutateAsync({ name, slug, blog_id: blogId });
- formData.set("name", "");
- formData.set("slug", "");
+ await createTag.mutateAsync({
+ name: title,
+ slug,
+ blog_id: blogId,
+ });
+
+ setTitle("");
+ setSlug("");
- target.reset();
toast.success("Tag created");
setOpen(false);
}}
diff --git a/apps/web/src/components/Tags/UpdateTagDialog.tsx b/apps/web/src/components/Tags/UpdateTagDialog.tsx
index dece635..334f3d0 100644
--- a/apps/web/src/components/Tags/UpdateTagDialog.tsx
+++ b/apps/web/src/components/Tags/UpdateTagDialog.tsx
@@ -3,18 +3,29 @@ import { Button } from "../ui/button";
import { Dialog, DialogContent } from "../ui/dialog";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
+import { useEffect, useState } from "react";
type Props = {
- tag: {
- tag_id: string | null;
- tag_name: string | null;
- slug: string | null;
- };
- onSubmit: (tag: { tag_name: string; slug: string }) => void;
+ tag:
+ | {
+ tag_id: string | null;
+ tag_name: string | null;
+ slug: string | null;
+ }
+ | undefined;
+ onSubmit: (tag: { tag_name: string; slug: string; tag_id: string }) => void;
open: boolean;
onOpenChange: (value: boolean) => void;
};
export function UpdateTagDialog({ onSubmit, tag, open, onOpenChange }: Props) {
+ const [newTitle, setNewTitle] = useState(tag?.tag_name || "");
+ const [newSlug, setNewSlug] = useState(tag?.slug || "");
+
+ useEffect(() => {
+ setNewTitle(tag?.tag_name || "");
+ setNewSlug(tag?.slug || "");
+ }, [tag]);
+
return (
- {tag.post_count}
+
+ {tag.post_count}
+
@@ -412,6 +418,10 @@ export default function BlogPosts() {
{
+ if (!tag) {
+ return;
+ }
+ setSelectedTag(tag);
setUpdateTagDialogOpen(true);
}}
>
@@ -420,7 +430,9 @@ export default function BlogPosts() {
{
- console.log("selected tag", tag);
+ if (!tag) {
+ return;
+ }
setSelectedTag(tag);
setDeleteTagDialogOpen(true);
}}
@@ -431,23 +443,6 @@ export default function BlogPosts() {
- {
- const res = await updateTagMutation.mutateAsync({
- name: newTag.tag_name,
- slug: newTag.slug,
- id: tag.tag_id!,
- });
- if (res.error) {
- toast.error("Failed to update tag");
- return;
- }
- toast.success("Tag updated");
- }}
- open={updateTagDialogOpen}
- onOpenChange={setUpdateTagDialogOpen}
- />
);
})}
@@ -456,6 +451,25 @@ export default function BlogPosts() {
+ {
+ if (!newTag.tag_id) return;
+ const res = await updateTagMutation.mutateAsync({
+ name: newTag.tag_name,
+ slug: newTag.slug,
+ id: newTag.tag_id,
+ });
+ if (res.error) {
+ toast.error("Failed to update tag");
+ return;
+ }
+ setUpdateTagDialogOpen(false);
+ toast.success("Tag updated");
+ }}
+ open={updateTagDialogOpen}
+ onOpenChange={setUpdateTagDialogOpen}
+ />
["tags", ...keys],
- tag: (tagId: string) => ["tag", tagId],
+ tags: (keys: string[]) => ["blog-tags", ...keys],
+ tag: (tagId: string) => ["blog-tag", tagId],
};
export function useTagsWithUsageQuery(
@@ -30,13 +30,17 @@ export function useTagsWithUsageQuery(
});
}
-export function useDeleteTagMutation() {
+export function useDeleteTagMutation(blogId: string) {
const queryClient = useQueryClient();
const supa = getSupabaseBrowserClient();
return useMutation(
async (tagId: string) => {
- const res = await supa.from("blog_tags").delete().eq("id", tagId);
+ const res = await supa
+ .from("blog_tags")
+ .delete()
+ .eq("id", tagId)
+ .eq("blog_id", blogId);
if (res.error) {
throw new Error(res.error.message);
@@ -46,13 +50,13 @@ export function useDeleteTagMutation() {
},
{
onSuccess: () => {
- queryClient.invalidateQueries(tagKeys.tags([""]));
+ queryClient.invalidateQueries(tagKeys.tags([blogId]));
},
}
);
}
-export function useUpdateTagMutation() {
+export function useUpdateTagMutation(blogId: string) {
const queryClient = useQueryClient();
const supa = getSupabaseBrowserClient();
@@ -68,7 +72,7 @@ export function useUpdateTagMutation() {
},
{
onSuccess: () => {
- queryClient.invalidateQueries(tagKeys.tags([""]));
+ queryClient.invalidateQueries(tagKeys.tags([blogId]));
},
}
);
diff --git a/apps/web/src/types/supabase.ts b/apps/web/src/types/supabase.ts
index 149017f..1f1d057 100644
--- a/apps/web/src/types/supabase.ts
+++ b/apps/web/src/types/supabase.ts
@@ -6,7 +6,7 @@ export type Json =
| { [key: string]: Json | undefined }
| Json[]
-export interface Database {
+export type Database = {
public: {
Tables: {
blog_tags: {
@@ -41,9 +41,10 @@ export interface Database {
{
foreignKeyName: "blog_tags_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
blogs: {
@@ -52,7 +53,7 @@ export interface Database {
description: string | null
emoji: string
id: string
- slug: string | null
+ slug: string
title: string
user_id: string
}
@@ -61,7 +62,7 @@ export interface Database {
description?: string | null
emoji: string
id?: string
- slug?: string | null
+ slug: string
title: string
user_id?: string
}
@@ -70,7 +71,7 @@ export interface Database {
description?: string | null
emoji?: string
id?: string
- slug?: string | null
+ slug?: string
title?: string
user_id?: string
}
@@ -78,15 +79,17 @@ export interface Database {
{
foreignKeyName: "blogs_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
{
foreignKeyName: "blogs_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
- }
+ },
]
}
feedback: {
@@ -157,61 +160,71 @@ export interface Database {
{
foreignKeyName: "post_tags_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "posts"
referencedColumns: ["id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "posts_with_blog_and_subscription_status"
referencedColumns: ["post_id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "posts_with_blog_and_subscription_status_v2"
referencedColumns: ["post_id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "posts_with_tags"
referencedColumns: ["post_id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "posts_with_tags_v2"
referencedColumns: ["post_id"]
},
{
foreignKeyName: "post_tags_post_id_fkey"
columns: ["post_id"]
+ isOneToOne: false
referencedRelation: "public_posts_v1"
referencedColumns: ["post_id"]
},
{
foreignKeyName: "post_tags_tag_id_fkey"
columns: ["tag_id"]
+ isOneToOne: false
referencedRelation: "blog_tags"
referencedColumns: ["id"]
},
{
foreignKeyName: "post_tags_tag_id_fkey"
columns: ["tag_id"]
- referencedRelation: "tag_usage_count_v1"
+ isOneToOne: false
+ referencedRelation: "tag_usage_count_v2"
referencedColumns: ["tag_id"]
- }
+ },
]
}
posts: {
Row: {
+ abstract: string
blog_id: string
content: Json
cover_image: string | null
@@ -227,6 +240,7 @@ export interface Database {
user_id: string
}
Insert: {
+ abstract?: string
blog_id: string
content?: Json
cover_image?: string | null
@@ -242,6 +256,7 @@ export interface Database {
user_id?: string
}
Update: {
+ abstract?: string
blog_id?: string
content?: Json
cover_image?: string | null
@@ -260,21 +275,24 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
},
{
foreignKeyName: "public_posts_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
{
foreignKeyName: "public_posts_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
- }
+ },
]
}
prices: {
@@ -345,15 +363,17 @@ export interface Database {
{
foreignKeyName: "subscriptions_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: true
referencedRelation: "users"
referencedColumns: ["id"]
},
{
foreignKeyName: "subscriptions_user_id_fkey"
columns: ["user_id"]
+ isOneToOne: true
referencedRelation: "users"
referencedColumns: ["id"]
- }
+ },
]
}
teams: {
@@ -382,19 +402,90 @@ export interface Database {
{
foreignKeyName: "public_teams_owner_id_fkey"
columns: ["owner_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
{
foreignKeyName: "public_teams_owner_id_fkey"
columns: ["owner_id"]
+ isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
- }
+ },
]
}
}
Views: {
+ post_tags_with_tags: {
+ Row: {
+ created_at: string | null
+ id: number | null
+ post_id: string | null
+ tag_description: string | null
+ tag_id: string | null
+ tag_name: string | null
+ tag_slug: string | null
+ }
+ Relationships: [
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "posts"
+ referencedColumns: ["id"]
+ },
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "posts_with_blog_and_subscription_status"
+ referencedColumns: ["post_id"]
+ },
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "posts_with_blog_and_subscription_status_v2"
+ referencedColumns: ["post_id"]
+ },
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "posts_with_tags"
+ referencedColumns: ["post_id"]
+ },
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "posts_with_tags_v2"
+ referencedColumns: ["post_id"]
+ },
+ {
+ foreignKeyName: "post_tags_post_id_fkey"
+ columns: ["post_id"]
+ isOneToOne: false
+ referencedRelation: "public_posts_v1"
+ referencedColumns: ["post_id"]
+ },
+ {
+ foreignKeyName: "post_tags_tag_id_fkey"
+ columns: ["tag_id"]
+ isOneToOne: false
+ referencedRelation: "blog_tags"
+ referencedColumns: ["id"]
+ },
+ {
+ foreignKeyName: "post_tags_tag_id_fkey"
+ columns: ["tag_id"]
+ isOneToOne: false
+ referencedRelation: "tag_usage_count_v2"
+ referencedColumns: ["tag_id"]
+ },
+ ]
+ }
posts_with_blog_and_subscription_status: {
Row: {
blog_id: string | null
@@ -415,9 +506,10 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
posts_with_blog_and_subscription_status_v2: {
@@ -441,9 +533,10 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
posts_with_tags: {
@@ -465,9 +558,10 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
posts_with_tags_v2: {
@@ -490,9 +584,10 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
public_posts_v1: {
@@ -517,12 +612,13 @@ export interface Database {
{
foreignKeyName: "posts_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
- tag_usage_count_v1: {
+ tag_usage_count_v2: {
Row: {
blog_id: string | null
created_at: string | null
@@ -536,9 +632,10 @@ export interface Database {
{
foreignKeyName: "blog_tags_blog_id_fkey"
columns: ["blog_id"]
+ isOneToOne: false
referencedRelation: "blogs"
referencedColumns: ["id"]
- }
+ },
]
}
users: {
@@ -687,3 +784,84 @@ export interface Database {
}
}
+type PublicSchema = Database[Extract]
+
+export type Tables<
+ PublicTableNameOrOptions extends
+ | keyof (PublicSchema["Tables"] & PublicSchema["Views"])
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
+ PublicSchema["Views"])
+ ? (PublicSchema["Tables"] &
+ PublicSchema["Views"])[PublicTableNameOrOptions] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : never
+
+export type TablesInsert<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : never
+
+export type TablesUpdate<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : never
+
+export type Enums<
+ PublicEnumNameOrOptions extends
+ | keyof PublicSchema["Enums"]
+ | { schema: keyof Database },
+ EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
+ : never = never,
+> = PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
+ : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
+ ? PublicSchema["Enums"][PublicEnumNameOrOptions]
+ : never
diff --git a/package.json b/package.json
index 0fc9d7a..e7b3b56 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"start": "concurrently --names \"APP,STRIPE\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run dev\" \"npm run stripe:webhook\"",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"db:typegen": "npx supabase gen types typescript --local --schema public > apps/web/src/types/supabase.ts",
+ "db:typegen:remote": "npx supabase gen types typescript --project-id ppfseefimhneysnokffx --schema public > apps/web/src/types/supabase.ts",
"db:start": "npx supabase start",
"db:stop": "npx supabase stop",
"db:open": "open http://localhost:54323",