sendou.ink/app/routes/u.$identifier/index.tsx
Kalle c2e43fb535
X Rank placements (#1325)
* Read in x rank placements script

* Xsearch initial

* XSearch initial

* XSearch with select

* Add badges

* XSearch player page initial

* Consider only one build by user for popular builds Closes #1312

* Revert "Side nav labels on hover (#1290)"

This reverts commit 6e839c6c2d.

* Added and fixed DAnish translations (#1315)

* Added Danish translations

Added Danish translations for the builds.json file.

Translations for the following variables have been added
stats.count.title # used the Danish word for "average" instead of "stats", as it makes more sense in Danish.
stats.ap.title
stats.percentage.title
stats.all
linkButton.abilityStats
linkButton.popularBuilds
noPopularBuilds

* Added Danish translations

Added Danish translations in the calander.json file

tag.desc.SZ
tag.desc.TW
tag.desc.S1
tag.desc.S2
tag.desc.SR
tag.desc.CARDS

* Added Danish translations

Added Danish translations in the common.json file

The following translations have been added
pages.vods
tag.name.SZ
tag.name.TW
tag.name.S1
tag.name.S2
tag.name.SR
tag.name.CARDS

* Added Danish translations in the faq.json file

Added the following Danish translations in the faq.json file

q7
a7

* Updated the DA/game-misc.json file

Added the names for the 3.0 maps.

* Added Danish translation for team.json

Added the following Danish translations for team.json file
- roles.MIDLINE

Fixed the following translation for the team.json file
- "roles.FRONTLINE": # fixed a typo

* Created a vods.json file for the Danish trans

* Fixed typo

Fixed the following translation in the Da\contributions.json file

"yaga" # fixed a Typo of "våben"

* Add Chinese Translation (#1314)

* Update contributions.json

* Update contributions.json

* Update faq.json

* Update user.json

* Create team.json

* Update common.json

* Update analyzer.json

* Prettier

* Prettier

* Prettier

* Update builds.json

* Update calendar.json

* Update common.json

* Update faq.json

* Update team.json

* Create vods.json

* Admin link player action

* Make PlacementTable rounded if only child

* Fix arrow disappeared

* Placements on user page initial

* Remove S2 site link

* Add badge

* Add feature to README

* Fix type error

* Different badge text if XP badge

* Add badge winners script

* Better user avatar + name wrapping for mobile

* Allow script to skip users

* Fix bad align when no weapons in weapon pool

* Add aliases to player name

* Add division icon to placements table

* Link to user page

* Link to xsearch on player details page

* Top 500 icons to user build page

* Working query but slow for weapons page

* Fix lint complaints

* Add cache to builds

* Remove useless SWR value

* Group months in xsearch

* Add xsearch to nav

* Add meta

* Remove TW for now

* Add splatoon3.ink as contributor

* Remove unneeded TODO

* Fix TODOs related to fetching monthYears

* Add FAQ question

* Leaderboards to nav

* Fix sploosh build stat pages returning 404

* Add badge

* Add article

* Patch 3.1

* Fix Prettier issue

* Add badge

* Add April SR gear

* Add badges

* Rename badge

* Add badge

* Add zh badge translation (#1322)

* Splatoon Competitive Guide article (#1316)

* Create splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Fix ToC Issues

* Update splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Update splatoon-competitive-guide.md

* Corrections

* Formatting

---------

Co-authored-by: Kalle <38327916+Sendouc@users.noreply.github.com>

* Fix typos

* Rename table and adjust columns

* Make cache ttl 0 in dev

* Make placements container a bit nicer for mobile

* Rename leftover xxx

* Placements script allow passing number as arg

* Skip leaderboards for now

* Add translations

* Rename placements folder to top-search

* Add placements to seed

* Add E2E tests

* Read in x rank placements script

* Xsearch initial

* XSearch initial

* XSearch with select

* XSearch player page initial

* Admin link player action

* Make PlacementTable rounded if only child

* Fix arrow disappeared

* Placements on user page initial

* Fix bad align when no weapons in weapon pool

* Add aliases to player name

* Add division icon to placements table

* Link to user page

* Link to xsearch on player details page

* Top 500 icons to user build page

* Working query but slow for weapons page

* Fix lint complaints

* Add cache to builds

* Remove useless SWR value

* Group months in xsearch

* Add xsearch to nav

* Add meta

* Remove TW for now

* Add splatoon3.ink as contributor

* Remove unneeded TODO

* Fix TODOs related to fetching monthYears

* Add FAQ question

* Leaderboards to nav

* Fix sploosh build stat pages returning 404

* Rename table and adjust columns

* Make cache ttl 0 in dev

* Make placements container a bit nicer for mobile

* Rename leftover xxx

* Placements script allow passing number as arg

* Skip leaderboards for now

* Add translations

* Rename placements folder to top-search

* Add placements to seed

* Add E2E tests

* Rename url variable and unify

* Tweaks

---------

Co-authored-by: Frederik <112972665+FrederikFraJylland@users.noreply.github.com>
Co-authored-by: Gell <61431488+gellneko@users.noreply.github.com>
Co-authored-by: Zen <99558412+zenpk@users.noreply.github.com>
Co-authored-by: Teddi <83455454+teddinotteddy@users.noreply.github.com>
2023-04-07 21:02:18 +03:00

289 lines
7.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 { Link, useMatches } from "@remix-run/react";
import clsx from "clsx";
import * as React from "react";
import invariant from "tiny-invariant";
import { Avatar } from "~/components/Avatar";
import { Badge } from "~/components/Badge";
import { Flag } from "~/components/Flag";
import { TwitchIcon } from "~/components/icons/Twitch";
import { TwitterIcon } from "~/components/icons/Twitter";
import { YouTubeIcon } from "~/components/icons/YouTube";
import { Image, WeaponImage } from "~/components/Image";
import { useTranslation } from "~/hooks/useTranslation";
import { modesShort } from "~/modules/in-game-lists";
import { type SendouRouteHandle } from "~/utils/remix";
import { rawSensToString } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
import { assertUnreachable } from "~/utils/types";
import {
modeImageUrl,
teamPage,
userSubmittedImage,
topSearchPlayerPage,
} from "~/utils/urls";
import { badgeExplanationText } from "../badges/$id";
import type { UserPageLoaderData } from "../u.$identifier";
export const handle: SendouRouteHandle = {
i18n: "badges",
};
export default function UserInfoPage() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
return (
<div className="u__container">
<div className="u__avatar-container">
<Avatar user={data} size="lg" className="u__avatar" />
<div>
<h2 className="u__name">
<div>{data.discordName}</div>
<div>
<span className="u__discriminator">
<wbr />#{data.discordDiscriminator}
</span>
{data.country ? <Flag countryCode={data.country} tiny /> : null}
</div>
</h2>
<TeamInfo />
</div>
<div className="u__socials">
{data.twitch ? (
<SocialLink type="twitch" identifier={data.twitch} />
) : null}
{data.twitter ? (
<SocialLink type="twitter" identifier={data.twitter} />
) : null}
{data.youtubeId ? (
<SocialLink type="youtube" identifier={data.youtubeId} />
) : null}
</div>
</div>
<ExtraInfos />
<WeaponPool />
<TopPlacements />
<BadgeContainer badges={data.badges} key={data.id} />
{data.bio && <article>{data.bio}</article>}
</div>
);
}
function TeamInfo() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const { team } = parentRoute.data as UserPageLoaderData;
if (!team) return null;
return (
<Link to={teamPage(team.customUrl)} className="u__team">
{team.avatarUrl ? (
<img
alt=""
src={userSubmittedImage(team.avatarUrl)}
width={24}
height={24}
className="rounded-full"
/>
) : null}
{team.name}
</Link>
);
}
interface SocialLinkProps {
type: "youtube" | "twitter" | "twitch";
identifier: string;
}
export function SocialLink({
type,
identifier,
}: {
type: "youtube" | "twitter" | "twitch";
identifier: string;
}) {
const href = () => {
switch (type) {
case "twitch":
return `https://www.twitch.tv/${identifier}`;
case "twitter":
return `https://www.twitter.com/${identifier}`;
case "youtube":
return `https://www.youtube.com/channel/${identifier}`;
default:
assertUnreachable(type);
}
};
return (
<a
className={clsx("u__social-link", {
youtube: type === "youtube",
twitter: type === "twitter",
twitch: type === "twitch",
})}
href={href()}
>
<SocialLinkIcon type={type} />
</a>
);
}
function SocialLinkIcon({ type }: Pick<SocialLinkProps, "type">) {
switch (type) {
case "twitch":
return <TwitchIcon />;
case "twitter":
return <TwitterIcon />;
case "youtube":
return <YouTubeIcon />;
default:
assertUnreachable(type);
}
}
function ExtraInfos() {
const { t } = useTranslation(["user"]);
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
const motionSensText =
typeof data.motionSens === "number"
? ` / ${t("user:motion")} ${rawSensToString(data.motionSens)}`
: "";
if (!data.inGameName && typeof data.stickSens !== "number") {
return null;
}
return (
<div className="u__extra-infos">
{data.inGameName && (
<div className="u__extra-info">
<span className="u__extra-info__heading">{t("user:ign.short")}</span>{" "}
{data.inGameName}
</div>
)}
{typeof data.stickSens === "number" && (
<div className="u__extra-info">
<span className="u__extra-info__heading">{t("user:sens")}</span>{" "}
{t("user:stick")} {rawSensToString(data.stickSens)}
{motionSensText}
</div>
)}
</div>
);
}
function WeaponPool() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
if (data.weapons.length === 0) return null;
return (
<div className="stack horizontal sm justify-center">
{data.weapons.map((weapon, i) => {
return (
<div key={weapon} className="u__weapon">
<WeaponImage
testId={`${weapon}-${i + 1}`}
weaponSplId={weapon}
variant="badge"
width={38}
height={38}
/>
</div>
);
})}
</div>
);
}
function TopPlacements() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
if (!data.playerId) return null;
return (
<Link
to={topSearchPlayerPage(data.playerId)}
className="u__placements"
data-testid="placements-box"
>
{modesShort.map((mode) => {
const placement = data.topPlacements[mode];
if (!placement) return null;
return (
<div key={mode} className="u__placements__mode">
<Image path={modeImageUrl(mode)} alt="" width={24} height={24} />
<div>
{placement.rank} / {placement.power}
</div>
</div>
);
})}
</Link>
);
}
function BadgeContainer(props: { badges: UserPageLoaderData["badges"] }) {
const { t } = useTranslation("badges");
const [badges, setBadges] = React.useState(props.badges);
const [bigBadge, ...smallBadges] = badges;
if (!bigBadge) return null;
const setBadgeFirst = (badge: Unpacked<UserPageLoaderData["badges"]>) => {
setBadges(
badges.map((b, i) => {
if (i === 0) return badge;
if (b.code === badge.code) return badges[0]!;
return b;
})
);
};
return (
<div>
<div
className={clsx("u__badges", {
"justify-center": smallBadges.length === 0,
})}
>
<Badge badge={bigBadge} size={125} isAnimated />
{smallBadges.length > 0 ? (
<div className="u__small-badges">
{smallBadges.map((badge) => (
<div key={badge.id} className="u__small-badge-container">
<Badge
badge={badge}
onClick={() => setBadgeFirst(badge)}
size={48}
isAnimated
/>
{badge.count > 1 ? (
<div className="u__small-badge-count">×{badge.count}</div>
) : null}
</div>
))}
</div>
) : null}
</div>
<div className="u__badge-explanation">
{badgeExplanationText(t, bigBadge)}
</div>
</div>
);
}