Can delete build

This commit is contained in:
Kalle
2022-09-03 15:00:43 +03:00
parent f43e51b377
commit d69f335866
8 changed files with 74 additions and 7 deletions

View File

@@ -10,12 +10,14 @@ import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
import { databaseTimestampToDate } from "~/utils/dates";
import { gearImageUrl, modeImageUrl, weaponImageUrl } from "~/utils/urls";
import { Ability } from "./Ability";
import { LinkButton } from "./Button";
import { Button, LinkButton } from "./Button";
import { FormWithConfirm } from "./FormWithConfirm";
import { Image } from "./Image";
import { Popover } from "./Popover";
type BuildProps = Pick<
Build,
| "id"
| "title"
| "description"
| "clothesGearSplId"
@@ -31,6 +33,7 @@ type BuildProps = Pick<
};
export function BuildCard({
id,
title,
description,
weapons,
@@ -42,7 +45,7 @@ export function BuildCard({
modes,
canEdit = false,
}: BuildProps) {
const { t } = useTranslation(["weapons", "builds"]);
const { t } = useTranslation(["weapons", "builds", "common"]);
const { i18n } = useTranslation();
const isMounted = useIsMounted();
@@ -67,6 +70,22 @@ export function BuildCard({
{t("builds:buildCard.edit")}
</LinkButton>
),
canEdit && (
<FormWithConfirm
key="delete"
dialogHeading={t("builds:deleteConfirm", { title })}
fields={[["buildToDeleteId", id]]}
>
<Button
className="build__small-text"
variant="minimal-destructive"
tiny
type="submit"
>
{t("common:actions.delete")}
</Button>
</FormWithConfirm>
),
].filter(Boolean);
return (

View File

@@ -32,6 +32,7 @@ export const BUILD = {
TITLE_MAX_LENGTH: 100,
DESCRIPTION_MAX_LENGTH: TWEET_LENGTH_MAX_LENGTH,
MAX_WEAPONS_COUNT: 5,
MAX_COUNT: 250,
} as const;
export const PLUS_TIERS = [1, 2, 3];

View File

@@ -0,0 +1,4 @@
delete from
"Build"
where
"id" = @id;

View File

@@ -2,20 +2,22 @@ import { sql } from "~/db/sql";
import type { Build, BuildAbility, BuildWeapon, GearType } from "~/db/types";
import type { ModeShort } from "~/modules/in-game-lists";
import { modesShort } from "~/modules/in-game-lists";
import invariant from "tiny-invariant";
import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
import createBuildSql from "./createBuild.sql";
import createBuildWeaponSql from "./createBuildWeapon.sql";
import createBuildAbilitySql from "./createBuildAbility.sql";
import countByUserIdSql from "./countByUserId.sql";
import buildsByUserIdSql from "./buildsByUserId.sql";
import invariant from "tiny-invariant";
import type { BuildAbilitiesTuple } from "~/modules/in-game-lists/types";
import deleteByIdSql from "./deleteById.sql";
const createBuildStm = sql.prepare(createBuildSql);
const createBuildWeaponStm = sql.prepare(createBuildWeaponSql);
const createBuildAbilityStm = sql.prepare(createBuildAbilitySql);
const countByUserIdStm = sql.prepare(countByUserIdSql);
const buildsByUserIdStm = sql.prepare(buildsByUserIdSql);
const deleteByIdStm = sql.prepare(deleteByIdSql);
interface CreateArgs {
ownerId: Build["ownerId"];
@@ -129,3 +131,7 @@ function dbAbilitiesToArrayOfArrays(
[sorted[8]!, sorted[9]!, sorted[10]!, sorted[11]!],
];
}
export function deleteById(id: Build["id"]) {
deleteByIdStm.run({ id });
}

View File

@@ -1,15 +1,42 @@
import type { ActionFunction } from "@remix-run/node";
import { json, type LoaderArgs } from "@remix-run/node";
import { useLoaderData, useMatches } from "@remix-run/react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { BuildCard } from "~/components/BuildCard";
import { LinkButton } from "~/components/Button";
import { Main } from "~/components/Main";
import { db } from "~/db";
import { getUser, useUser } from "~/modules/auth";
import { getUser, requireUser, useUser } from "~/modules/auth";
import { atOrError } from "~/utils/arrays";
import { notFoundIfFalsy } from "~/utils/remix";
import { notFoundIfFalsy, parseRequestFormData } from "~/utils/remix";
import { actualNumber, id } from "~/utils/zod";
import { type UserPageLoaderData, userParamsSchema } from "../../u.$identifier";
const buildsActionSchema = z.object({
buildToDeleteId: z.preprocess(actualNumber, id),
});
export const action: ActionFunction = async ({ request }) => {
const user = await requireUser(request);
const data = await parseRequestFormData({
request,
schema: buildsActionSchema,
});
if (
!db.builds
.buildsByUserId(user.id)
.some((build) => build.id === data.buildToDeleteId)
) {
throw new Response(null, { status: 400 });
}
db.builds.deleteById(data.buildToDeleteId);
return null;
};
export const handle = {
i18n: ["weapons", "builds"],
};
@@ -34,6 +61,7 @@ export default function UserBuildsPage() {
const parentPageData = atOrError(useMatches(), -2).data as UserPageLoaderData;
const data = useLoaderData<typeof loader>();
// xxx: disable button if too many builds or something
return (
<Main className="stack lg">
<div className="stack items-end">
@@ -46,6 +74,7 @@ export default function UserBuildsPage() {
{data.builds.map((build) => (
<BuildCard
key={build.id}
id={build.id}
title={build.title}
description={build.description}
headGearSplId={build.headGearSplId}

View File

@@ -123,6 +123,11 @@ export const action: ActionFunction = async ({ request }) => {
schema: newBuildActionSchema,
});
const usersBuilds = db.builds.buildsByUserId(user.id);
if (usersBuilds.length >= BUILD.MAX_COUNT) {
throw new Response(null, { status: 400 });
}
db.builds.create({
title: data.title,
description: data.description,

View File

@@ -9,5 +9,7 @@
"forms.weapons": "Weapons",
"forms.gear.HEAD": "Gear (head)",
"forms.gear.CLOTHES": "Gear (clothes)",
"forms.gear.SHOES": "Gear (shoes)"
"forms.gear.SHOES": "Gear (shoes)",
"deleteConfirm": "Delete build '{{title}}'?"
}

View File

@@ -23,6 +23,7 @@
"actions.edit": "Edit",
"actions.add": "Add",
"actions.remove": "Remove",
"actions.delete": "Delete",
"results": "Results",