mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 09:20:24 -05:00
157 lines
4.3 KiB
TypeScript
157 lines
4.3 KiB
TypeScript
import { useFetcher } from "@remix-run/react";
|
|
import * as React from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useCopyToClipboard } from "react-use";
|
|
import { SendouButton } from "~/components/elements/Button";
|
|
import { CheckmarkIcon } from "~/components/icons/Checkmark";
|
|
import { ClipboardIcon } from "~/components/icons/Clipboard";
|
|
import { PlusIcon } from "~/components/icons/Plus";
|
|
import { SubmitButton } from "~/components/SubmitButton";
|
|
import { useTrusted } from "~/hooks/swr";
|
|
import {
|
|
SENDOU_INK_BASE_URL,
|
|
SENDOUQ_PREPARING_PAGE,
|
|
sendouQInviteLink,
|
|
} from "~/utils/urls";
|
|
import type { SendouQPreparingAction } from "../actions/q.preparing.server";
|
|
import styles from "./MemberAdder.module.css";
|
|
|
|
export function MemberAdder({
|
|
inviteCode,
|
|
groupMemberIds,
|
|
}: {
|
|
inviteCode: string;
|
|
groupMemberIds: number[];
|
|
}) {
|
|
const { t } = useTranslation(["q"]);
|
|
const [truster, setTruster] = React.useState<number>();
|
|
const fetcher = useFetcher<SendouQPreparingAction>();
|
|
const inviteLink = `${SENDOU_INK_BASE_URL}${sendouQInviteLink(inviteCode)}`;
|
|
const [state, copyToClipboard] = useCopyToClipboard();
|
|
const [copySuccess, setCopySuccess] = React.useState(false);
|
|
|
|
const showMemberAddError = fetcher.data?.error === "taken";
|
|
|
|
const groupMembersJoined = groupMemberIds.join(",");
|
|
// biome-ignore lint/correctness/useExhaustiveDependencies: biome migration
|
|
React.useEffect(() => {
|
|
setTruster(undefined);
|
|
}, [groupMembersJoined]);
|
|
|
|
React.useEffect(() => {
|
|
if (!state.value) return;
|
|
|
|
setCopySuccess(true);
|
|
const timeout = setTimeout(() => setCopySuccess(false), 2000);
|
|
|
|
return () => clearTimeout(timeout);
|
|
}, [state]);
|
|
|
|
return (
|
|
<div className="stack md flex-wrap justify-center">
|
|
<div>
|
|
<label htmlFor="invite">{t("q:looking.groups.adder.inviteLink")}</label>
|
|
<div className="stack horizontal sm items-center">
|
|
<input
|
|
type="text"
|
|
value={inviteLink}
|
|
readOnly
|
|
id="invite"
|
|
className={styles.input}
|
|
/>
|
|
<SendouButton
|
|
variant={copySuccess ? "outlined-success" : "outlined"}
|
|
onPress={() => copyToClipboard(inviteLink)}
|
|
icon={copySuccess ? <CheckmarkIcon /> : <ClipboardIcon />}
|
|
aria-label="Copy to clipboard"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<fetcher.Form method="post" action={SENDOUQ_PREPARING_PAGE}>
|
|
<label htmlFor="players">{t("q:looking.groups.adder.quickAdd")}</label>
|
|
<div className="stack horizontal sm items-center">
|
|
<TrusterDropdown
|
|
setTruster={setTruster}
|
|
groupMemberIds={groupMemberIds}
|
|
/>
|
|
<SubmitButton
|
|
variant="outlined"
|
|
_action="ADD_TRUSTED"
|
|
isDisabled={!truster}
|
|
icon={<PlusIcon />}
|
|
/>
|
|
</div>
|
|
</fetcher.Form>
|
|
{showMemberAddError ? (
|
|
<div className="text-xxs text-center font-bold text-error">
|
|
{t("q:looking.groups.adder.error")}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TrusterDropdown({
|
|
setTruster,
|
|
groupMemberIds,
|
|
}: {
|
|
setTruster: (id: number | undefined) => void;
|
|
groupMemberIds: number[];
|
|
}) {
|
|
const { t } = useTranslation(["q"]);
|
|
const { trusters, teams } = useTrusted();
|
|
|
|
if (!trusters || trusters.length === 0) {
|
|
return <select name="id" id="players" disabled className={styles.input} />;
|
|
}
|
|
|
|
const trustersNotInGroup = trusters.filter(
|
|
(truster) => !groupMemberIds.includes(truster.id),
|
|
);
|
|
|
|
const othersOptions = trustersNotInGroup
|
|
.filter((player) => !player.teamId)
|
|
.map((player) => {
|
|
return (
|
|
<option key={player.id} value={player.id}>
|
|
{player.username}
|
|
</option>
|
|
);
|
|
});
|
|
|
|
return (
|
|
<select
|
|
name="id"
|
|
id="players"
|
|
onChange={(e) =>
|
|
setTruster(e.target.value ? Number(e.target.value) : undefined)
|
|
}
|
|
className={styles.input}
|
|
>
|
|
<option value="">{t("q:looking.groups.adder.select")}</option>
|
|
{teams?.map((team) => {
|
|
return (
|
|
<optgroup label={team.name} key={team.id}>
|
|
{trustersNotInGroup
|
|
.filter((player) => player.teamId === team.id)
|
|
.map((player) => {
|
|
return (
|
|
<option key={player.id} value={player.id}>
|
|
{player.username}
|
|
</option>
|
|
);
|
|
})}
|
|
</optgroup>
|
|
);
|
|
})}
|
|
{teams && teams.length > 0 && othersOptions.length > 0 ? (
|
|
<optgroup label={t("q:looking.groups.adder.others")}>
|
|
{othersOptions}
|
|
</optgroup>
|
|
) : (
|
|
othersOptions
|
|
)}
|
|
</select>
|
|
);
|
|
}
|