From f56e8ff238c9b280e4da2b726e9101c92c90cdd7 Mon Sep 17 00:00:00 2001 From: Kyle Montag Date: Thu, 26 Dec 2024 00:48:16 -0500 Subject: [PATCH] Formation share ID (#63) * setup new field * actually save the share id from the form * add copy formation share id button * better button placement and copy * PR feedback --- components/Builder.tsx | 1 + components/CopyFormationShareId.tsx | 23 +++ components/FormationCard.tsx | 27 +++- components/SaveButton.tsx | 25 +++- drizzle/migrations/0003_red_nebula.sql | 1 + drizzle/migrations/meta/0003_snapshot.json | 165 +++++++++++++++++++++ drizzle/migrations/meta/_journal.json | 7 + drizzle/schema.ts | 3 + lib/server/formations.ts | 6 +- 9 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 components/CopyFormationShareId.tsx create mode 100644 drizzle/migrations/0003_red_nebula.sql create mode 100644 drizzle/migrations/meta/0003_snapshot.json diff --git a/components/Builder.tsx b/components/Builder.tsx index 29b393d..15abee4 100644 --- a/components/Builder.tsx +++ b/components/Builder.tsx @@ -206,6 +206,7 @@ export default function Builder({ data, formation: _formation }: Props) { name={_formation?.name} id={_formation?.id} tags={tags} + formationShareId={_formation?.formationShareId} /> )} + ); +} diff --git a/components/FormationCard.tsx b/components/FormationCard.tsx index dec151f..98ec580 100644 --- a/components/FormationCard.tsx +++ b/components/FormationCard.tsx @@ -20,6 +20,7 @@ import LikeFormationButton from "@/components/LikeFormationButton"; import DeleteFormationButton from "@/components/DeleteFormationButton"; import ShareFormationButton from "@/components/ShareFormationButton"; import { Edit2 } from "lucide-react"; +import CopyFormationShareId from "@/components/CopyFormationShareId"; type FormationCardProps = { data: FormationData; @@ -42,8 +43,17 @@ export default function FormationCard({ isLink, currentUserId, }: FormationCardProps) { - const { id, formation, artifact, username, user_image, name, votes, tags } = - data; + const { + id, + formation, + artifact, + username, + user_image, + name, + votes, + tags, + formationShareId, + } = data; const layout = Math.trunc(data.layout); @@ -59,11 +69,16 @@ export default function FormationCard({ <> -
-

{name}

-
- {tags && tags.map((tag) => {tag})} +
+
+

{name}

+
+ {tags && tags.map((tag) => {tag})} +
+ {formationShareId && ( + + )}
diff --git a/components/SaveButton.tsx b/components/SaveButton.tsx index 3eb2280..9a5f461 100644 --- a/components/SaveButton.tsx +++ b/components/SaveButton.tsx @@ -30,6 +30,7 @@ type SaveButtonProps = { name?: string; id?: number; tags?: string[]; + formationShareId?: string; }; export default function SaveButton({ @@ -40,6 +41,7 @@ export default function SaveButton({ name, id, tags, + formationShareId, }: SaveButtonProps) { const [open, setOpen] = useState(false); const router = useRouter(); @@ -47,10 +49,13 @@ export default function SaveButton({ const handleSave = async (e: FormEvent) => { e.preventDefault(); - const { name } = e.target as typeof e.target & { + const { name, formationShareId } = e.target as typeof e.target & { name: { value: string }; + formationShareId: { value: string }; }; + console.log("formationShareId", formationShareId); + if (formation.filter((x) => x != "").length === 0) { toast.error("Formation is empty!"); setOpen(false); @@ -69,6 +74,7 @@ export default function SaveButton({ user_id: user.id, name: name.value, tags, + formationShareId: formationShareId.value, }; const requestUrl = id ? `/api/formations/${id}` : "/api/formations"; @@ -85,6 +91,7 @@ export default function SaveButton({ formation: formation.join(","), id: id || -1, tags: JSON.stringify(formationData.tags), + formationShareId: formationData.formationShareId || "", }; try { @@ -120,6 +127,7 @@ export default function SaveButton({ className="px-4" onSubmit={handleSave} name={name} + formationShareId={formationShareId} /> @@ -136,7 +144,8 @@ function SaveFormationForm({ className, onSubmit, name, -}: React.ComponentProps<"form">) { + formationShareId, +}: React.ComponentProps<"form"> & { formationShareId?: string }) { return (
+ +
+ + +
); diff --git a/drizzle/migrations/0003_red_nebula.sql b/drizzle/migrations/0003_red_nebula.sql new file mode 100644 index 0000000..2e85399 --- /dev/null +++ b/drizzle/migrations/0003_red_nebula.sql @@ -0,0 +1 @@ +ALTER TABLE `formations` ADD `formationShareId` text(255) DEFAULT ''; diff --git a/drizzle/migrations/meta/0003_snapshot.json b/drizzle/migrations/meta/0003_snapshot.json new file mode 100644 index 0000000..b802d50 --- /dev/null +++ b/drizzle/migrations/meta/0003_snapshot.json @@ -0,0 +1,165 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e7282cd4-6540-40c3-a421-4ad5d75bc115", + "prevId": "d46d90ae-6248-41cc-99cf-5270c94282af", + "tables": { + "formations": { + "name": "formations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "formation": { + "name": "formation", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "artifact": { + "name": "artifact", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "layout": { + "name": "layout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "formationShareId": { + "name": "formationShareId", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "roster": { + "name": "roster", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "last_update": { + "name": "last_update", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "roster_user_id_unique": { + "name": "roster_user_id_unique", + "columns": ["user_id"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "votes": { + "name": "votes", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "formation_id": { + "name": "formation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "votes_formation_id_user_id_unique": { + "name": "votes_formation_id_user_id_unique", + "columns": ["formation_id", "user_id"], + "isUnique": true + } + }, + "foreignKeys": { + "votes_formation_id_formations_id_fk": { + "name": "votes_formation_id_formations_id_fk", + "tableFrom": "votes", + "tableTo": "formations", + "columnsFrom": ["formation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index ead5b4d..5a96d1c 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1730151629218, "tag": "0002_medical_puck", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1735074547469, + "tag": "0003_red_nebula", + "breakpoints": true } ] } diff --git a/drizzle/schema.ts b/drizzle/schema.ts index e10db14..09542f0 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -8,6 +8,9 @@ export const formations = sqliteTable("formations", { userId: text("user_id", { length: 255 }).notNull(), name: text("name", { length: 255 }).notNull(), tags: text("tags", { mode: "json" }).$type().default([]).notNull(), + formationShareId: text("formationShareId", { length: 255 }) + .default("") + .notNull(), }); export const votes = sqliteTable( diff --git a/lib/server/formations.ts b/lib/server/formations.ts index 8a93b84..6bf183a 100644 --- a/lib/server/formations.ts +++ b/lib/server/formations.ts @@ -5,7 +5,7 @@ import { auth } from "@clerk/nextjs/server"; import { type ClerkUser, getUser } from "@/lib/server/users"; import { type FormationData } from "@/lib/formations"; -import { eq, sql, desc, and } from "drizzle-orm"; +import { eq, sql, desc } from "drizzle-orm"; import { formations, votes, type FormationWithVotes } from "@/drizzle/schema"; import { drizzleClient } from "@/lib/server/drizzle"; import { heroNameToId } from "./cms-data"; @@ -125,6 +125,7 @@ async function _mostPopularFormations(limit: number): Promise { layout: formations.layout, userId: formations.userId, tags: formations.tags, + formationShareId: formations.formationShareId, }) .from(formations) .leftJoin(votes, eq(formations.id, votes.formationId)) @@ -153,6 +154,7 @@ type FormationCreateData = { layout: string; name: string; tags: string[]; + formationShareId?: string; }; export async function createFormation( @@ -166,6 +168,7 @@ export async function createFormation( userId: userId!, name: formation.name, tags: formation.tags, + formationShareId: formation.formationShareId, }; const createResponse = await drizzle .insert(formations) @@ -199,6 +202,7 @@ export async function updateFormation( layout: parseInt(formation.layout), name: formation.name, tags: formation.tags, + formationShareId: formation.formationShareId, }) .where(eq(formations.id, id)) .execute();