import clsx from "clsx"; import { add, sub } from "date-fns"; import React from "react"; import { useTranslation } from "react-i18next"; import type { MetaFunction } from "react-router"; import { useFetcher, useLoaderData } from "react-router"; import { AddNewButton } from "~/components/AddNewButton"; import { Alert } from "~/components/Alert"; import { Main } from "~/components/Main"; import { SubmitButton } from "~/components/SubmitButton"; import { useUser } from "~/features/auth/core/user"; import { useSearchParamStateEncoder } from "~/hooks/useSearchParamState"; import { databaseTimestampToDate } from "~/utils/dates"; import { metaTags, type SerializeFrom } from "~/utils/remix"; import type { SendouRouteHandle } from "~/utils/remix.server"; import type { Unpacked } from "~/utils/types"; import { LFG_PAGE, lfgNewPostPage, navIconUrl } from "~/utils/urls"; import { action } from "../actions/lfg.server"; import { LFGAddFilterButton } from "../components/LFGAddFilterButton"; import { LFGFilters } from "../components/LFGFilters"; import { LFGPost } from "../components/LFGPost"; import { filterPosts } from "../core/filtering"; import { LFG } from "../lfg-constants"; import { filterToSmallStr, type LFGFilter, smallStrToFilter, } from "../lfg-types"; import { loader } from "../loaders/lfg.server"; import styles from "./lfg.module.css"; export { loader, action }; export const handle: SendouRouteHandle = { i18n: ["lfg"], breadcrumb: () => ({ imgPath: navIconUrl("lfg"), href: LFG_PAGE, type: "IMAGE", }), }; export const meta: MetaFunction = (args) => { return metaTags({ title: "LFG", ogTitle: "Splatoon LFG (looking for players, teams & coaches)", description: "Find people to play Splatoon with. Create a post or browse existing ones. For looking players, teams, scrim partners and coaches alike.", location: args.location, }); }; export type LFGLoaderData = SerializeFrom; export type LFGLoaderPost = Unpacked; export type TiersMap = ReturnType; const unserializeTiers = (data: SerializeFrom) => new Map(data.tiersMap); function decodeURLQuery(queryString: string): LFGFilter[] { if (queryString === "") { return []; } return queryString .split("-") .map(smallStrToFilter) .filter((x) => x !== null); } function encodeURLQuery(filters: LFGFilter[]): string { return filters.map(filterToSmallStr).join("-"); } export default function LFGPage() { const { t } = useTranslation(["common", "lfg"]); const user = useUser(); const data = useLoaderData(); const [filterFromSearch, setTilterFromSearch] = useSearchParamStateEncoder({ defaultValue: [], name: "q", revive: decodeURLQuery, encode: encodeURLQuery, }); const [filters, _setFilters] = React.useState(filterFromSearch); const setFilters = (x: LFGFilter[]) => { setTilterFromSearch(x); _setFilters(x); }; const tiersMap = React.useMemo(() => unserializeTiers(data), [data]); const filteredPosts = filterPosts(data.posts, filters, tiersMap); const showExpiryAlert = (post: Unpacked) => { if (post.author.id !== user?.id) return false; const expiryDate = add(databaseTimestampToDate(post.updatedAt), { days: LFG.POST_FRESHNESS_DAYS, }); const expiryCloseDate = sub(expiryDate, { days: 7 }); if (new Date() < expiryCloseDate) return false; return true; }; return (
setFilters([...filters, newFilter])} filters={filters} />
setFilters( filters.map((filter) => filter._tag === newFilter._tag ? newFilter : filter, ), ) } removeFilterByTag={(tag) => setFilters(filters.filter((filter) => filter._tag !== tag)) } /> {filteredPosts.map((post) => (
{showExpiryAlert(post) ? : null}
))} {filteredPosts.length === 0 ? (
{t("lfg:noPosts")}
) : null}
); } function PostExpiryAlert({ postId }: { postId: number }) { const { t } = useTranslation(["common", "lfg"]); const fetcher = useFetcher(); return ( {t("lfg:expiring")}{" "} {t("common:actions.clickHere")} ); }