BuildCard bottom row buttons

This commit is contained in:
Kalle 2022-09-01 19:23:38 +03:00
parent 9090d19b57
commit 788da90437
7 changed files with 97 additions and 40 deletions

View File

@ -10,6 +10,7 @@ import type {
import { databaseTimestampToDate } from "~/utils/dates";
import { gearImageUrl, modeImageUrl, weaponImageUrl } from "~/utils/urls";
import { Ability } from "./Ability";
import { LinkButton } from "./Button";
import { Image } from "./Image";
import { Popover } from "./Popover";
@ -26,6 +27,7 @@ type BuildProps = Pick<
abilities: AllAbilitiesTuple;
modes: ModeShort[] | null;
weapons: Array<BuildWeapon["weaponSplId"]>;
canEdit?: boolean;
};
export function BuildCard({
@ -38,11 +40,35 @@ export function BuildCard({
shoesGearSplId,
abilities,
modes,
canEdit = false,
}: BuildProps) {
const { t } = useTranslation("weapons");
const { t } = useTranslation(["weapons", "builds"]);
const { i18n } = useTranslation();
const isMounted = useIsMounted();
const bottomRowItems = [
description && (
<Popover
key="info"
trigger={<>{t("builds:buildCard.info")}</>}
triggerClassName="build__small-text"
>
{description}
</Popover>
),
canEdit && (
<LinkButton
key="edit"
className="build__small-text"
variant="minimal"
tiny
to="/"
>
{t("builds:buildCard.edit")}
</LinkButton>
),
].filter(Boolean);
return (
<div className="build">
<div>
@ -81,15 +107,17 @@ export function BuildCard({
<div key={weaponSplId} className="build__weapon">
<Image
path={weaponImageUrl(weaponSplId)}
alt={t(`${weaponSplId}` as any)}
title={t(`${weaponSplId}` as any)}
alt={t(`weapons:${weaponSplId}` as any)}
title={t(`weapons:${weaponSplId}` as any)}
height={36}
width={36}
/>
</div>
))}
{weapons.length === 1 && (
<div className="build__weapon-text">{t(`${weapons[0]!}` as any)}</div>
<div className="build__weapon-text">
{t(`weapons:${weapons[0]!}` as any)}
</div>
)}
</div>
<div className="build__gear-abilities">
@ -109,14 +137,8 @@ export function BuildCard({
gearId={shoesGearSplId}
/>
</div>
{description && (
<div className="build__bottom-row">
<Popover
trigger={<button className="build__bottom-row-button">Info</button>}
>
{description}
</Popover>
</div>
{bottomRowItems.length > 0 && (
<div className="build__bottom-row">{bottomRowItems}</div>
)}
</div>
);

View File

@ -1,4 +1,5 @@
import { Popover as HeadlessPopover } from "@headlessui/react";
import clsx from "clsx";
import * as React from "react";
import { usePopper } from "react-popper";
@ -6,9 +7,11 @@ import { usePopper } from "react-popper";
export function Popover({
children,
trigger,
triggerClassName,
}: {
children: React.ReactNode;
trigger: React.ReactNode;
triggerClassName?: string;
}) {
const [referenceElement, setReferenceElement] = React.useState();
const [popperElement, setPopperElement] = React.useState();
@ -19,7 +22,7 @@ export function Popover({
<HeadlessPopover.Button
// @ts-expect-error Popper docs: https://popper.js.org/react-popper/v2/
ref={setReferenceElement}
className="minimal tiny"
className={clsx("minimal tiny", triggerClassName)}
>
{trigger}
</HeadlessPopover.Button>

View File

@ -1,45 +1,69 @@
import { json, type LoaderArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { useLoaderData, useMatches } from "@remix-run/react";
import { useTranslation } from "react-i18next";
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 { atOrError } from "~/utils/arrays";
import { notFoundIfFalsy } from "~/utils/remix";
import { userParamsSchema } from "../u.$identifier";
import { type UserPageLoaderData, userParamsSchema } from "../u.$identifier";
export const handle = {
i18n: "weapons",
i18n: ["weapons", "builds"],
};
export const loader = ({ params }: LoaderArgs) => {
export const loader = async ({ params, request }: LoaderArgs) => {
const loggedInUser = await getUser(request);
const { identifier } = userParamsSchema.parse(params);
const user = notFoundIfFalsy(db.users.findByIdentifier(identifier));
const builds = db.builds.buildsByUserId(user.id);
if (builds.length === 0 && loggedInUser?.id !== user.id) {
throw new Response(null, { status: 404 });
}
return json({ builds });
};
export default function UserBuildsPage() {
const { t } = useTranslation("builds");
const user = useUser();
const parentPageData = atOrError(useMatches(), -2).data as UserPageLoaderData;
const data = useLoaderData<typeof loader>();
return (
<Main>
<div className="builds-container">
{data.builds.map((build) => (
<BuildCard
key={build.id}
title={build.title}
description={build.description}
headGearSplId={build.headGearSplId}
clothesGearSplId={build.clothesGearSplId}
shoesGearSplId={build.shoesGearSplId}
modes={build.modes}
updatedAt={build.updatedAt}
abilities={build.abilities}
weapons={build.weapons}
/>
))}
<Main className="stack lg">
<div className="stack items-end">
<LinkButton to="new" tiny>
{t("addBuild")}
</LinkButton>
</div>
{data.builds.length > 0 ? (
<div className="builds-container">
{data.builds.map((build) => (
<BuildCard
key={build.id}
title={build.title}
description={build.description}
headGearSplId={build.headGearSplId}
clothesGearSplId={build.clothesGearSplId}
shoesGearSplId={build.shoesGearSplId}
modes={build.modes}
updatedAt={build.updatedAt}
abilities={build.abilities}
weapons={build.weapons}
canEdit={user?.id === parentPageData.id}
/>
))}
</div>
) : (
<div className="text-center text-lg text-lighter font-semi-bold">
{t("noBuilds")}
</div>
)}
</Main>
);
}

View File

@ -737,13 +737,9 @@ dialog::backdrop {
height: 100%;
align-items: flex-end;
justify-content: center;
gap: var(--s-2);
}
.build__bottom-row-button {
height: 1.25rem;
background-color: transparent;
color: var(--theme);
font-size: var(--fonts-xxs);
padding-block: var(--s-1);
padding-inline: var(--s-1-5);
.build__small-text {
font-size: var(--fonts-xxs) !important;
}

View File

@ -90,6 +90,10 @@
align-items: flex-start;
}
.items-end {
align-items: flex-end;
}
.justify-center {
justify-content: center;
}

View File

@ -0,0 +1,6 @@
{
"addBuild": "Add build",
"noBuilds": "No builds yet. Add your first one!",
"buildCard.info": "Info",
"buildCard.edit": "Edit"
}

View File

@ -8,6 +8,7 @@ import type user from "../public/locales/en/user.json";
import type badges from "../public/locales/en/badges.json";
import type calendar from "../public/locales/en/calendar.json";
import type weapons from "../public/locales/en/weapons.json";
import type builds from "../public/locales/en/builds.json";
declare module "react-i18next" {
interface CustomTypeOptions {
@ -21,6 +22,7 @@ declare module "react-i18next" {
badges: typeof badges;
calendar: typeof calendar;
weapons: typeof weapons;
builds: typeof builds;
};
}
}