import { Link, useFetcher } from "@remix-run/react";
import clsx from "clsx";
import { formatDistanceToNow } from "date-fns";
import React from "react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Divider } from "~/components/Divider";
import { SendouButton } from "~/components/elements/Button";
import { Flag } from "~/components/Flag";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image, TierImage, WeaponImage } from "~/components/Image";
import { EditIcon } from "~/components/icons/Edit";
import { TrashIcon } from "~/components/icons/Trash";
import { useUser } from "~/features/auth/core/user";
import * as Seasons from "~/features/mmr/core/Seasons";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useHasRole } from "~/modules/permissions/hooks";
import { databaseTimestampToDate } from "~/utils/dates";
import { lfgNewPostPage, navIconUrl, userPage } from "~/utils/urls";
import { hourDifferenceBetweenTimezones } from "../core/timezone";
import type { LFGLoaderData, TiersMap } from "../routes/lfg";
import styles from "./LFGPost.module.css";
type Post = LFGLoaderData["posts"][number];
export function LFGPost({
post,
tiersMap,
}: {
post: Post;
tiersMap: TiersMap;
}) {
if (post.team) {
return (
);
}
return ;
}
const USER_POST_EXPANDABLE_CRITERIA = 300;
function UserLFGPost({ post, tiersMap }: { post: Post; tiersMap: TiersMap }) {
const user = useUser();
const isAdmin = useHasRole("ADMIN");
const [isExpanded, setIsExpanded] = React.useState(false);
return (
{post.author.id === user?.id || isAdmin ? (
) : null}
);
}
function TeamLFGPost({
post,
tiersMap,
}: {
post: Post & { team: NonNullable };
tiersMap: TiersMap;
}) {
const isMounted = useIsMounted();
const user = useUser();
const isAdmin = useHasRole("ADMIN");
const [isExpanded, setIsExpanded] = React.useState(false);
return (
{post.author.id === user?.id ? (
) : null}
{isExpanded ? (
) : (
)}
{post.author.id === user?.id || isAdmin ? (
) : null}
);
}
function PostTeamLogoHeader({ team }: { team: NonNullable }) {
return (
{team.avatarUrl ?
: null}
{team.name}
);
}
function PostTeamMembersPeek({
team,
tiersMap,
}: {
team: NonNullable;
tiersMap: TiersMap;
}) {
return (
{team.members.map((member) => (
))}
);
}
function PostTeamMembersFull({
team,
tiersMap,
postId,
}: {
team: NonNullable;
tiersMap: TiersMap;
postId: number;
}) {
return (
{team.members.map((member) => (
))}
);
}
function PostTeamMember({
member,
tiersMap,
}: {
member: NonNullable["members"][number];
tiersMap: TiersMap;
}) {
const tiers = tiersMap.get(member.id);
const tier = tiers?.latest ?? tiers?.previous;
return (
{member.username}
{tier ?
: null}
);
}
function PostUserHeader({
author,
includeWeapons,
}: {
author: Post["author"];
includeWeapons: boolean;
}) {
return (
{author.username}
{" "}
{author.country ? : null}
{includeWeapons ? (
{author.weaponPool.map(({ weaponSplId, isFavorite }) => (
))}
) : null}
);
}
function PostTime({
createdAt,
updatedAt,
}: {
createdAt: number;
updatedAt: number;
}) {
const { t, i18n } = useTranslation(["lfg"]);
const createdAtDate = databaseTimestampToDate(createdAt);
const updatedAtDate = databaseTimestampToDate(updatedAt);
const overDayDifferenceBetween =
updatedAtDate.getTime() - createdAtDate.getTime() > 1000 * 60 * 60 * 24;
return (
{createdAtDate.toLocaleString(i18n.language, {
month: "long",
day: "numeric",
})}{" "}
{overDayDifferenceBetween ? (
({t("lfg:post.lastActive")}{" "}
{formatDistanceToNow(updatedAtDate, {
addSuffix: true,
})}
)
) : null}
);
}
function PostPills({
timezone,
plusTier,
languages,
tiers,
canEdit,
postId,
}: {
timezone?: string | null;
plusTier?: number | null;
languages?: string | null;
tiers?: NonNullable>;
canEdit?: boolean;
postId: number;
}) {
const isMounted = useIsMounted();
return (
{typeof timezone === "string" && isMounted && (
)}
{!isMounted &&
}
{typeof plusTier === "number" && (
)}
{tiers &&
}
{typeof languages === "string" && (
)}
{canEdit &&
}
);
}
function PostTimezonePillPlaceholder() {
return ;
}
const currentSeasonNth = Seasons.currentOrPrevious()!.nth;
function PostSkillPills({
tiers,
}: {
tiers: NonNullable>;
}) {
const hasBoth = tiers.latest && tiers.previous;
return (
{tiers.latest ? (
) : null}
{tiers.previous ? (
) : null}
);
}
function PostSkillPill({
seasonNth,
tier,
cut,
}: {
seasonNth: number;
tier: TieredSkill["tier"];
cut?: "START" | "END";
}) {
return (
S{seasonNth}
);
}
function PostPlusServerPill({ plusTier }: { plusTier: number }) {
return (
{plusTier}
);
}
function PostTimezonePill({ timezone }: { timezone: string }) {
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const diff = hourDifferenceBetweenTimezones(userTimezone, timezone);
const textColorClass = () => {
const absDiff = Math.abs(diff);
if (absDiff <= 3) {
return "text-success";
}
if (absDiff <= 6) {
return "text-warning";
}
return "text-error";
};
return (
{diff === 0 ? "±" : ""}
{diff > 0 ? "+" : ""}
{diff}h
);
}
function PostLanguagePill({ languages }: { languages: string }) {
return (
{languages.replace(/,/g, " / ").toUpperCase()}
);
}
function PostTextTypeHeader({ type }: { type: Post["type"] }) {
const { t } = useTranslation(["lfg"]);
return (
{t(`lfg:types.${type}`)}
);
}
function PostEditButton({ id }: { id: number }) {
const { t } = useTranslation(["common"]);
return (
{t("common:actions.edit")}
);
}
function PostDeleteButton({ id, type }: { id: number; type: Post["type"] }) {
const fetcher = useFetcher();
const { t } = useTranslation(["common", "lfg"]);
return (
}
>
{t("common:actions.delete")}
);
}
function PostExpandableText({
text,
isExpanded: _isExpanded,
setIsExpanded,
expandableCriteria,
}: {
text: string;
isExpanded: boolean;
setIsExpanded: (isExpanded: boolean) => void;
expandableCriteria?: number;
}) {
const { t } = useTranslation(["common"]);
const isExpandable = !expandableCriteria || text.length > expandableCriteria;
const isExpanded = !isExpandable ? true : _isExpanded;
return (
{text}
{isExpandable ? (
setIsExpanded(!isExpanded)}
className={clsx([styles.showAllButton], {
[styles.showAllButtonExpanded]: isExpanded,
})}
variant="outlined"
size="small"
>
{isExpanded
? t("common:actions.showLess")
: t("common:actions.showMore")}
) : null}
{!isExpanded ?
: null}
);
}