hensei-web/components/PartyDetails/index.tsx

353 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useSnapshot } from "valtio";
import { useTranslation } from "next-i18next";
import Linkify from "react-linkify";
import classNames from "classnames";
import * as AlertDialog from "@radix-ui/react-alert-dialog";
import CrossIcon from "~public/icons/Cross.svg";
import Button from "~components/Button";
import CharLimitedFieldset from "~components/CharLimitedFieldset";
import RaidDropdown from "~components/RaidDropdown";
import TextFieldset from "~components/TextFieldset";
import { accountState } from "~utils/accountState";
import { appState } from "~utils/appState";
import "./index.scss";
import Link from "next/link";
import { formatTimeAgo } from "~utils/timeAgo";
const emptyRaid: Raid = {
id: "",
name: {
en: "",
ja: "",
},
slug: "",
level: 0,
group: 0,
element: 0,
};
// Props
interface Props {
editable: boolean;
updateCallback: (name?: string, description?: string, raid?: Raid) => void;
deleteCallback: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => void;
}
const PartyDetails = (props: Props) => {
const { party, raids } = useSnapshot(appState);
const { account } = useSnapshot(accountState);
const { t } = useTranslation("common");
const router = useRouter();
const locale = router.locale || "en";
const nameInput = React.createRef<HTMLInputElement>();
const descriptionInput = React.createRef<HTMLTextAreaElement>();
const raidSelect = React.createRef<HTMLSelectElement>();
const readOnlyClasses = classNames({
PartyDetails: true,
ReadOnly: true,
Visible: !party.detailsVisible,
});
const editableClasses = classNames({
PartyDetails: true,
Editable: true,
Visible: party.detailsVisible,
});
const emptyClasses = classNames({
EmptyDetails: true,
Visible: !party.detailsVisible,
});
const userClass = classNames({
user: true,
empty: !party.user,
});
const linkClass = classNames({
wind: party && party.element == 1,
fire: party && party.element == 2,
water: party && party.element == 3,
earth: party && party.element == 4,
dark: party && party.element == 5,
light: party && party.element == 6,
});
const [errors, setErrors] = useState<{ [key: string]: string }>({
name: "",
description: "",
});
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
event.preventDefault();
const { name, value } = event.target;
let newErrors = errors;
setErrors(newErrors);
}
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
event.preventDefault();
const { name, value } = event.target;
let newErrors = errors;
setErrors(newErrors);
}
function toggleDetails() {
appState.party.detailsVisible = !appState.party.detailsVisible;
}
function updateDetails(event: React.MouseEvent) {
const nameValue = nameInput.current?.value;
const descriptionValue = descriptionInput.current?.value;
const raid = raids.find((raid) => raid.slug === raidSelect.current?.value);
props.updateCallback(nameValue, descriptionValue, raid);
toggleDetails();
}
const userImage = () => {
if (party.user)
return (
<img
alt={party.user.picture.picture}
className={`profile ${party.user.picture.element}`}
srcSet={`/profile/${party.user.picture.picture}.png,
/profile/${party.user.picture.picture}@2x.png 2x`}
src={`/profile/${party.user.picture.picture}.png`}
/>
);
else return <div className="no-user" />;
};
const userBlock = () => {
return (
<div className={userClass}>
{userImage()}
{party.user ? party.user.username : t("no_user")}
</div>
);
};
const linkedUserBlock = (user: User) => {
return (
<div>
<Link href={`/${user.username}`} passHref>
<a className={linkClass}>{userBlock()}</a>
</Link>
</div>
);
};
const linkedRaidBlock = (raid: Raid) => {
return (
<div>
<Link href={`/teams?raid=${raid.slug}`} passHref>
<a className={`Raid ${linkClass}`}>{raid.name[locale]}</a>
</Link>
</div>
);
};
const deleteButton = () => {
if (party.editable) {
return (
<AlertDialog.Root>
<AlertDialog.Trigger className="Button destructive">
<span className="icon">
<CrossIcon />
</span>
<span className="text">{t("buttons.delete")}</span>
</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay className="Overlay" />
<AlertDialog.Content className="Dialog">
<AlertDialog.Title className="DialogTitle">
{t("modals.delete_team.title")}
</AlertDialog.Title>
<AlertDialog.Description className="DialogDescription">
{t("modals.delete_team.description")}
</AlertDialog.Description>
<div className="actions">
<AlertDialog.Cancel className="Button modal">
{t("modals.delete_team.buttons.cancel")}
</AlertDialog.Cancel>
<AlertDialog.Action
className="Button modal destructive"
onClick={(e) => props.deleteCallback(e)}
>
{t("modals.delete_team.buttons.confirm")}
</AlertDialog.Action>
</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
);
} else {
return "";
}
};
const editable = (
<section className={editableClasses}>
<CharLimitedFieldset
fieldName="name"
placeholder="Name your team"
value={party.name}
limit={50}
onChange={handleInputChange}
error={errors.name}
ref={nameInput}
/>
<RaidDropdown
showAllRaidsOption={false}
currentRaid={party.raid?.slug || ""}
ref={raidSelect}
/>
<TextFieldset
fieldName="name"
placeholder={
"Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediels 1 first\nGood luck with RNG!"
}
value={party.description}
onChange={handleTextAreaChange}
error={errors.description}
ref={descriptionInput}
/>
<div className="bottom">
<div className="left">
{router.pathname !== "/new" ? deleteButton() : ""}
</div>
<div className="right">
<Button active={true} onClick={toggleDetails}>
{t("buttons.cancel")}
</Button>
<Button active={true} icon="check" onClick={updateDetails}>
{t("buttons.save_info")}
</Button>
</div>
</div>
</section>
);
const readOnly = (
<section className={readOnlyClasses}>
<div className="info">
<div className="left">
{party.name ? <h1>{party.name}</h1> : ""}
<div className="attribution">
{party.user ? linkedUserBlock(party.user) : userBlock()}
{party.raid ? linkedRaidBlock(party.raid) : ""}
{party.created_at != undefined ? (
<time
className="last-updated"
dateTime={new Date(party.created_at).toString()}
>
{formatTimeAgo(new Date(party.created_at), locale)}
</time>
) : (
""
)}
</div>
</div>
<div className="right">
{party.editable ? (
<Button active={true} icon="edit" onClick={toggleDetails}>
{t("buttons.show_info")}
</Button>
) : (
<div />
)}
</div>
</div>
{party.description ? (
<p>
<Linkify>{party.description}</Linkify>
</p>
) : (
""
)}
</section>
);
const emptyDetails = (
<div className={emptyClasses}>
{party.editable ? (
<Button active={true} icon="edit" onClick={toggleDetails}>
{t("buttons.show_info")}
</Button>
) : (
<div />
)}
</div>
);
const generateTitle = () => {
let title = party.raid ? `[${party.raid?.name[locale]}] ` : "";
const username =
party.user != null ? `@${party.user?.username}` : t("header.anonymous");
if (party.name != null)
title += t("header.byline", {
partyName: party.name,
username: username,
});
else if (party.name == null && party.editable && router.route === "/new")
title = t("header.new_team");
else
title += t("header.untitled_team", {
username: username,
});
return title;
};
return (
<div>
<Head>
<title>{generateTitle()}</title>
<meta property="og:title" content={generateTitle()} />
<meta
property="og:description"
content={party.description ? party.description : ""}
/>
<meta property="og:url" content="https://app.granblue.team" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="app.granblue.team" />
<meta name="twitter:title" content={generateTitle()} />
<meta
name="twitter:description"
content={party.description ? party.description : ""}
/>
</Head>
{editable && (party.name || party.description || party.raid)
? readOnly
: emptyDetails}
{editable}
</div>
);
};
export default PartyDetails;