Skip to content

Commit

Permalink
Add tags to "edit song"
Browse files Browse the repository at this point in the history
- show the four fundamental tags
- correctly update m2m table when changed
  • Loading branch information
dvnrsn committed Dec 13, 2023
1 parent 0027e4d commit ce02838
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 5 deletions.
25 changes: 23 additions & 2 deletions app/models/song.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export function getSong({
wallCounts: true,
startingWeightFoot: true,

tags: {
select: {
tag: true,
},
},

...(userId
? {
createdAt: true,
Expand Down Expand Up @@ -59,11 +65,26 @@ type RequiredSongField = Pick<Song, "title" | "artist">;
export function editSong(
songId: Song["id"],
userId: User["id"],
songData: NullableSongFields & RequiredSongField,
songData: NullableSongFields & RequiredSongField & { tags?: string[] },
) {
let tags = {};
if (songData.tags) {
// avoid deleting and reinserting tags all the time, only if they changed.
// also the nested ternary freaks out prisma TS for some reason
tags = {
deleteMany: {},
create: songData.tags?.map((tag) => ({
tag: { connect: { id: parseInt(tag) } },
})),
};
}
return prisma.song.update({
where: { id: songId },
data: { ...songData, updatedById: userId },
data: {
...songData,
updatedById: userId,
tags,
},
});
}

Expand Down
9 changes: 9 additions & 0 deletions app/models/tags.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { prisma } from "~/db.server";

export function getTags() {
return prisma.tag.findMany({
orderBy: {
name: "asc",
},
});
}
91 changes: 88 additions & 3 deletions app/routes/songs.$songId_.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import invariant from "tiny-invariant";
import { z } from "zod";

import { deleteSong, editSong, getSong } from "~/models/song.server";
import { getTags } from "~/models/tags.server";
import { requireAdmin } from "~/session.server";

const schema = z.object({
Expand All @@ -26,28 +27,48 @@ const schema = z.object({
danceCounts: z.coerce.number().optional(),
wallCounts: z.coerce.number().optional(),
startingWeightFoot: z.string().optional(),
tags: z.array(z.string()).optional(),
});

export const action = async ({ request, params }: LoaderFunctionArgs) => {
invariant(params.songId, "songId not found");
const user = await requireAdmin(request);
const formData = await request.formData();

const { _action, ...payload } = Object.fromEntries(formData);
const { _action, tag, originalTags, ...payload } =

Check failure on line 38 in app/routes/songs.$songId_.edit.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'tag' is assigned a value but never used
Object.fromEntries(formData);

if (_action === "delete") {
await deleteSong({ id: parseInt(params.songId) });
return redirect("/songs");
}

const tags = formData.getAll("tag");

payload["tags"] = tags as unknown as FormDataEntryValue;

const result = schema.safeParse(payload);

if (typeof originalTags !== "string") {
return json({
payload,
error: { originalTags: "originalTags must be a string" },
});
}

const originalTagsArr = originalTags.split(", ");

if (!result.success) {
return json({
payload,
error: result.error.flatten().fieldErrors,
});
}

if (originalTagsArr.sort().join(",") === tags.sort().join(",")) {
delete result.data.tags;
}

const song = await editSong(parseInt(params.songId), user.id, result.data);
return redirect("/songs/" + song.id);
};
Expand All @@ -62,7 +83,16 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
if (!song) {
throw new Response("Not Found", { status: 404 });
}
return json({ song });
const unfilteredTags = await getTags();
const tags = unfilteredTags.filter((tag) =>
[
"Wednesday Lessons",
"Friday Lessons",
"Early Night",
"Late Night",
].includes(tag.name),
);
return json({ song, tags });
};

export default function SongEditPage() {
Expand All @@ -71,7 +101,7 @@ export default function SongEditPage() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();

const { song } = data;
const { song, tags } = data;
return (
<div className="max-w-[800px] mx-auto">
<Form method="post">
Expand Down Expand Up @@ -210,6 +240,61 @@ export default function SongEditPage() {
defaultValue={song.startingWeightFoot || ""}
/>
</div>
<div className="flex flex-col justify-around">
<label className="cursor-pointer py-2 md:py-0">
<input
type="checkbox"
name="tag"
value={tags.find((tag) => tag.name === "Wednesday Lessons")?.id}
defaultChecked={song.tags?.some(
(tag) => tag.tag.name === "Wednesday Lessons",
)}
/>{" "}
Wednesday Lessons
</label>
<label className="cursor-pointer py-2 md:py-0">
<input
type="checkbox"
name="tag"
value={tags.find((tag) => tag.name === "Friday Lessons")?.id}
defaultChecked={song.tags?.some(
(tag) => tag.tag.name === "Friday Lessons",
)}
/>{" "}
Friday Lessons
</label>
</div>
<div className="flex gap-8">
<label className="cursor-pointer py-2 md:py-0">
<input
type="checkbox"
name="tag"
value={tags.find((tag) => tag.name === "Early Night")?.id}
defaultChecked={song.tags?.some(
(tag) => tag.tag.name === "Early Night",
)}
/>{" "}
Early Night
</label>
<label className="cursor-pointer py-2 md:py-0">
<input
type="checkbox"
name="tag"
value={tags.find((tag) => tag.name === "Late Night")?.id}
defaultChecked={song.tags?.some(
(tag) => tag.tag.name === "Late Night",
)}
/>{" "}
Late Night
</label>
<input
type="text"
name="originalTags"
defaultValue={song.tags?.map((tag) => tag.tag.id).join(", ")}
readOnly
className="hidden"
/>
</div>
</div>
<div className="flex gap-4 mt-8 justify-end">
<button
Expand Down

0 comments on commit ce02838

Please sign in to comment.