Skip to content

Commit

Permalink
implement pet update
Browse files Browse the repository at this point in the history
  • Loading branch information
LexSwed committed Aug 18, 2024
1 parent 1af9a06 commit 9e8ed88
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 33 deletions.
11 changes: 9 additions & 2 deletions packages/web/src/lib/pet-home-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,20 @@ export const PetHomeCard = (props: PetHomeCardProps) => {
};

export function PetPicture(props: {
pet: { pictureUrl?: string | null; species: DatabasePet["species"] };
pet: {
pictureUrl?: string | null;
gender: DatabasePet["gender"];
species: DatabasePet["species"];
};
class?: string;
}) {
return (
<div
class={tw(
"flex size-16 shrink-0 items-center justify-center rounded-full bg-tertiary/10 text-tertiary",
"flex size-16 shrink-0 items-center justify-center rounded-full",
props.pet.gender && props.pet.gender === "female"
? "bg-primary/10 text-primary"
: "bg-tertiary/10 text-tertiary",
props.class,
)}
>
Expand Down
70 changes: 57 additions & 13 deletions packages/web/src/routes/app/pets/[petId]/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { Button, ButtonLink, Card, Icon, Option, Picker, Popover, Text, TextField } from "@nou/ui";
import {
Button,
ButtonLink,
Card,
Fieldset,
Form,
Icon,
Option,
Picker,
Popover,
Text,
TextField,
} from "@nou/ui";
import { Title } from "@solidjs/meta";
import { type RouteDefinition, type RouteSectionProps, createAsync } from "@solidjs/router";
import {
type RouteDefinition,
type RouteSectionProps,
createAsync,
useSubmission,
} from "@solidjs/router";
import { parseISO } from "date-fns";
import { For } from "solid-js";
import { Show, createMemo } from "solid-js";

import { AppHeader } from "~/lib/app-header";
import { PetPicture } from "~/lib/pet-home-card";
import { GenderSwitch } from "~/lib/species-selector";
import { getPetForEdit } from "~/server/api/pet";
import { getPetForEdit, updatePet } from "~/server/api/pet";
import { getUser, getUserProfile } from "~/server/api/user";
import type { DatabasePet } from "~/server/db/schema";
import { T, cacheTranslations, createTranslator } from "~/server/i18n";
Expand Down Expand Up @@ -65,6 +83,7 @@ function PetPictureWithUpload(props: {
name: string;
pictureUrl: string | null;
species: DatabasePet["species"];
gender: DatabasePet["gender"];
};
}) {
const t = createTranslator("pets");
Expand Down Expand Up @@ -96,6 +115,7 @@ function PetUpdateForm(props: { petId: string }) {
const pet = createAsync(() => getPetForEdit(props.petId!));
const t = createTranslator("pets");
const user = createAsync(() => getUser());
const updateSubmission = useSubmission(updatePet);

const monthNames = createMemo(() => {
const u = user();
Expand All @@ -112,16 +132,35 @@ function PetUpdateForm(props: { petId: string }) {
});
});

const birthDate = createMemo(() => {
const dateOfBirth = pet()?.dateOfBirth;
if (!dateOfBirth) return { bday: undefined, bmonth: undefined, byear: undefined };
const date = parseISO(dateOfBirth);
return {
bday: date.getDate(),
bmonth: date.getMonth(),
byear: date.getFullYear(),
};
});

return (
<Show when={pet()}>
{(pet) => (
<form class="flex flex-col gap-8">
<TextField label={t("edit.name")} value={pet().name} class="flex-[2]" required />
<Form
class="flex flex-col gap-8"
action={updatePet}
validationErrors={updateSubmission.result?.errors}
>
<input type="hidden" name="petId" value={pet().id} />
<TextField
name="name"
label={t("edit.name")}
value={pet().name}
class="flex-[2]"
required
/>
<GenderSwitch name="gender" value={pet().gender} />
<fieldset>
<Text as="legend" with="label-sm" class="mb-2 inline-block">
{t("edit.birth-date")}
</Text>
<Fieldset name="dateOfBirth" legend={t("edit.birth-date")}>
<div class="flex flex-row gap-2">
<TextField
name="bday"
Expand All @@ -131,16 +170,18 @@ function PetUpdateForm(props: { petId: string }) {
min="1"
max="31"
class="flex-1"
value={birthDate().bday}
/>
<Picker
label={t("edit.birth-month")}
name="bmonth"
autocomplete="off"
class="flex-[2]"
value={birthDate().bmonth}
>
<Option value="" label={t("edit.birth-month-none")} />
<For each={monthNames()}>
{(month, index) => <Option value={index() + 1} label={month} />}
{(month, index) => <Option value={index()} label={month} />}
</For>
</Picker>
<TextField
Expand All @@ -151,12 +192,15 @@ function PetUpdateForm(props: { petId: string }) {
min="1990"
max={new Date().getFullYear()}
class="flex-1"
value={birthDate().byear}
/>
</div>
</fieldset>
</Fieldset>
<TextField label={t("edit.breed")} name="breed" value={pet().breed ?? ""} />
<Button type="submit">{t("edit.save-cta")}</Button>
</form>
<Button type="submit" loading={updateSubmission.pending}>
{t("edit.save-cta")}
</Button>
</Form>
)}
</Show>
);
Expand Down
27 changes: 15 additions & 12 deletions packages/web/src/server/api/pet.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as v from "valibot";

import { getRequestUser } from "~/server/auth/request-user";
import { petCreate } from "~/server/db/queries/petCreate";
import { UpdatePetSchema, petUpdate } from "~/server/db/queries/petUpdate";
import { getUpdatePetSchema, petUpdate } from "~/server/db/queries/petUpdate";
import { userPets } from "~/server/db/queries/userPets";
import { type ErrorKeys, translateErrorTokens } from "~/server/utils";

Expand Down Expand Up @@ -73,13 +73,7 @@ const PetBirthDaySchema = v.pipe(
),
bmonth: v.nullish(
v.config(
v.pipe(
v.unknown(),
v.transform(Number),
v.number(),
v.minValue(0),
v.maxValue(new Date().getFullYear()),
),
v.pipe(v.unknown(), v.transform(Number), v.number(), v.minValue(0), v.maxValue(11)),
{ message: "bmonth" satisfies ErrorKeys },
),
0,
Expand Down Expand Up @@ -142,7 +136,7 @@ export const updatePetWeightServer = async (formData: FormData) => {
if (!petId) {
throw new Error("petId is not provided");
}
const { weight } = v.parse(v.required(UpdatePetSchema, ["weight"]), {
const { weight } = v.parse(v.required(v.pick(getUpdatePetSchema(), ["weight"])), {
weight: formData.get("weight"),
});
const currentUser = await getRequestUser();
Expand Down Expand Up @@ -172,7 +166,7 @@ export const updatePetBreedServer = async (formData: FormData) => {
if (!petId) {
throw new Error("petId is not provided");
}
const { breed } = v.parse(v.required(UpdatePetSchema, ["breed"]), {
const { breed } = v.parse(v.required(v.pick(getUpdatePetSchema(), ["breed"])), {
breed: formData.get("breed"),
});
const currentUser = await getRequestUser();
Expand All @@ -191,6 +185,7 @@ export const updatePetBreedServer = async (formData: FormData) => {
{ status: 422, revalidate: [] },
);
}
console.error(error);
return json({ failed: true, errors: null }, { status: 500, revalidate: [] });
}
};
Expand All @@ -201,11 +196,18 @@ export const updatePetServer = async (formData: FormData) => {
if (!petId) {
throw new Error("petId is not provided");
}
const dateOfBirth = v.parse(PetBirthDaySchema, {
let dateOfBirth: Date | undefined;
const birthDateData = {
bday: formData.get("bday"),
bmonth: formData.get("bmonth"),
byear: formData.get("byear"),
});
};
console.log(birthDateData);
// if one of the fields provided - validate, skip otherwise
if (birthDateData.byear || birthDateData.bmonth || birthDateData.bday) {
dateOfBirth = v.parse(PetBirthDaySchema, birthDateData);
console.log({ dateOfBirth });
}
const currentUser = await getRequestUser();
const pet = await petUpdate(
{
Expand All @@ -226,6 +228,7 @@ export const updatePetServer = async (formData: FormData) => {
{ status: 422, revalidate: [] },
);
}
console.error(error);
return json({ failed: true, errors: null }, { status: 500, revalidate: [] });
}
};
6 changes: 6 additions & 0 deletions packages/web/src/server/api/pet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getUserPetsServer,
updatePetBirthDateServer,
updatePetBreedServer,
updatePetServer,
updatePetWeightServer,
} from "./pet.server";

Expand All @@ -32,6 +33,11 @@ export const updatePetBreed = action(
"update-pet-breed",
);

export const updatePet = action(
async (formData: FormData) => updatePetServer(formData),
"update-pet",
);

export const getPet = cache(async (petId: string) => getPetServer(petId), "user-pet");

export const getPetForEdit = cache(
Expand Down
15 changes: 9 additions & 6 deletions packages/web/src/server/db/queries/petUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useDb } from "~/server/db";
import { type DatabasePet, type UserID, petTable } from "~/server/db/schema";
import type { ErrorKeys } from "~/server/utils";

export const UpdatePetSchema = v.object({
const UpdatePetSchema = v.object({
name: v.optional(
v.pipe(
v.string("createPet.name.required" satisfies ErrorKeys),
Expand All @@ -24,12 +24,12 @@ export const UpdatePetSchema = v.object({
),
gender: v.optional(v.picklist(["male", "female"], "createPet.gender" satisfies ErrorKeys)),
breed: v.optional(
v.config(v.pipe(v.string(), v.trim(), v.minLength(2), v.maxLength(200)), {
v.config(v.pipe(v.string(), v.trim(), v.maxLength(200)), {
message: "createPet.breed" satisfies ErrorKeys,
}),
),
color: v.optional(
v.config(v.pipe(v.string(), v.trim(), v.minLength(2), v.maxLength(200)), {
v.config(v.pipe(v.string(), v.trim(), v.maxLength(200)), {
message: "createPet.color" satisfies ErrorKeys,
}),
),
Expand Down Expand Up @@ -58,6 +58,7 @@ export async function petUpdate(
UpdatePetSchema,
petData,
);
console.log(dateOfBirth?.toISOString());
const db = useDb();
const pet = await db
.update(petTable)
Expand All @@ -66,9 +67,9 @@ export async function petUpdate(
name,
species,
gender,
breed,
color,
dateOfBirth: dateOfBirth?.toUTCString(),
breed: breed === "" ? null : breed,
color: color === "" ? null : color,
dateOfBirth: dateOfBirth ? dateOfBirth.toISOString() : undefined,
})
.where(and(eq(petTable.id, petId), eq(petTable.ownerId, userId)))
.returning({
Expand All @@ -85,3 +86,5 @@ export async function petUpdate(

return pet;
}

export const getUpdatePetSchema = () => UpdatePetSchema;

0 comments on commit 9e8ed88

Please sign in to comment.