mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 07:32:19 -05:00
BuildCard bottom row buttons
This commit is contained in:
parent
9090d19b57
commit
788da90437
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@
|
|||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
|||
6
public/locales/en/builds.json
Normal file
6
public/locales/en/builds.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"addBuild": "Add build",
|
||||
"noBuilds": "No builds yet. Add your first one!",
|
||||
"buildCard.info": "Info",
|
||||
"buildCard.edit": "Edit"
|
||||
}
|
||||
2
types/react-i18next.d.ts
vendored
2
types/react-i18next.d.ts
vendored
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user