mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-12 14:16:04 -05:00
Merge branch 'rewrite' of https://github.com/Sendouc/sendou.ink into rewrite
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Link, useLocation } from "@remix-run/react";
|
||||
import { Link, useMatches } from "@remix-run/react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { RootLoaderData } from "~/root";
|
||||
import { type SendouRouteHandle } from "~/utils/remix";
|
||||
import { LOGO_PATH, navIconUrl } from "~/utils/urls";
|
||||
import { Image } from "../Image";
|
||||
import { ColorModeToggle } from "./ColorModeToggle";
|
||||
@@ -12,6 +13,25 @@ import { Menu } from "./Menu";
|
||||
import navItems from "./nav-items.json";
|
||||
import { UserItem } from "./UserItem";
|
||||
|
||||
function useActiveNavItem() {
|
||||
const matches = useMatches();
|
||||
|
||||
return React.useMemo(() => {
|
||||
let activeItem: { name: string; url: string } | undefined = undefined;
|
||||
|
||||
for (const match of matches.reverse()) {
|
||||
const handle = match.handle as SendouRouteHandle | undefined;
|
||||
|
||||
if (handle?.navItemName) {
|
||||
activeItem = navItems.find(({ name }) => name === handle.navItemName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return activeItem;
|
||||
}, [matches]);
|
||||
}
|
||||
|
||||
export const Layout = React.memo(function Layout({
|
||||
children,
|
||||
patrons,
|
||||
@@ -22,12 +42,8 @@ export const Layout = React.memo(function Layout({
|
||||
isCatchBoundary?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||
|
||||
const currentPagesNavItem = navItems.find((navItem) =>
|
||||
location.pathname.includes(navItem.name)
|
||||
);
|
||||
const activeNavItem = useActiveNavItem();
|
||||
|
||||
return (
|
||||
<div className="layout__container">
|
||||
@@ -51,15 +67,15 @@ export const Layout = React.memo(function Layout({
|
||||
</div>
|
||||
</header>
|
||||
<Menu expanded={menuOpen} closeMenu={() => setMenuOpen(false)} />
|
||||
{currentPagesNavItem && (
|
||||
{activeNavItem && (
|
||||
<div className="layout__page-title-header">
|
||||
<Image
|
||||
path={navIconUrl(currentPagesNavItem.name)}
|
||||
path={navIconUrl(activeNavItem.name)}
|
||||
width={28}
|
||||
height={28}
|
||||
alt=""
|
||||
/>
|
||||
{t(`pages.${currentPagesNavItem.name}` as any)}
|
||||
{t(`pages.${activeNavItem.name}` as any)}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
|
||||
@@ -18,7 +18,11 @@ import { Main } from "~/components/Main";
|
||||
import { requireUser } from "~/modules/auth";
|
||||
import { getUser, isImpersonating } from "~/modules/auth/user.server";
|
||||
import { canPerformAdminActions } from "~/permissions";
|
||||
import { parseRequestFormData, validate } from "~/utils/remix";
|
||||
import {
|
||||
parseRequestFormData,
|
||||
type SendouRouteHandle,
|
||||
validate,
|
||||
} from "~/utils/remix";
|
||||
import { makeTitle } from "~/utils/strings";
|
||||
import { impersonateUrl, SEED_URL, STOP_IMPERSONATING_URL } from "~/utils/urls";
|
||||
import { db } from "~/db";
|
||||
@@ -69,6 +73,10 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
navItemName: "admin",
|
||||
};
|
||||
|
||||
export default function AdminPage() {
|
||||
return (
|
||||
<Main className="stack lg">
|
||||
|
||||
@@ -52,6 +52,7 @@ export const links: LinksFunction = () => {
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: ["weapons", "analyzer"],
|
||||
navItemName: "analyzer",
|
||||
};
|
||||
|
||||
export default function BuildAnalyzerPage() {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface BadgesLoaderData {
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: "badges",
|
||||
navItemName: "badges",
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ export const links: LinksFunction = () => {
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: ["weapons", "builds"],
|
||||
breadcrumb: ({ t }) => t("pages.builds"),
|
||||
navItemName: "builds",
|
||||
};
|
||||
|
||||
export default function BuildsLayoutPage() {
|
||||
|
||||
@@ -48,6 +48,7 @@ export const meta: MetaFunction = (args) => {
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: "calendar",
|
||||
navItemName: "calendar",
|
||||
};
|
||||
|
||||
const loaderSearchParamsSchema = z.object({
|
||||
|
||||
@@ -66,6 +66,7 @@ export const meta: MetaFunction = (args) => {
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: "game-misc",
|
||||
navItemName: "maps",
|
||||
};
|
||||
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
|
||||
@@ -3,11 +3,16 @@ import { Outlet } from "@remix-run/react";
|
||||
import { Main } from "~/components/Main";
|
||||
import { SubNav, SubNavLink } from "~/components/SubNav";
|
||||
import styles from "~/styles/plus.css";
|
||||
import { type SendouRouteHandle } from "~/utils/remix";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
};
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
navItemName: "plus",
|
||||
};
|
||||
|
||||
export default function PlusPageLayout() {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { type TFunction, type Namespace } from "react-i18next";
|
||||
import { type RouteMatch } from "@remix-run/react";
|
||||
import type navItems from "~/components/layout/nav-items.json";
|
||||
|
||||
export function notFoundIfFalsy<T>(value: T | null | undefined): T {
|
||||
if (!value) throw new Response(null, { status: 404 });
|
||||
@@ -102,6 +103,7 @@ export function validate(condition: any, status = 400): asserts condition {
|
||||
export type SendouRouteHandle = {
|
||||
/** The i18n translation files used for this route, via remix-i18next */
|
||||
i18n?: Namespace;
|
||||
|
||||
/**
|
||||
* A function that returns the breadcrumb text that should be displayed in
|
||||
* the <Breadcrumb> component
|
||||
@@ -110,4 +112,7 @@ export type SendouRouteHandle = {
|
||||
match: RouteMatch;
|
||||
t: TFunction<"common", undefined>;
|
||||
}) => string | undefined;
|
||||
|
||||
/** The name of a navItem that is active on this route. See nav-items.json */
|
||||
navItemName?: typeof navItems[number]["name"];
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"inYourTimeZone": " Alle Zeiten sind konvertiert zur lokalen Zeitzone:",
|
||||
"addNew": "Turnier hinzufügen",
|
||||
"inYourTimeZone": "Alle Zeiten sind konvertiert zur lokalen Zeitzone:",
|
||||
"addNew": "Event hinzufügen",
|
||||
"noEvents": "Keine Events in dieser Woche",
|
||||
"reportResults": "Ergebnisse können eingetragen werden für:",
|
||||
"day": "Tag {{number}}",
|
||||
@@ -8,19 +8,21 @@
|
||||
"participatedCount": "{{count}} teilnehmende Teams",
|
||||
"members": "Mitglieder",
|
||||
"results": "Resultate",
|
||||
"createMapList": "Arenen-Liste erstellen",
|
||||
|
||||
"forms.dates": "Datum",
|
||||
"forms.bracketUrl": "Turnierbaum URL",
|
||||
"forms.discordInvite": "Discord server Einladung URL",
|
||||
"forms.bracketUrl": "Turnierbaum-URL",
|
||||
"forms.discordInvite": "Discord-Server Einladungs-URL",
|
||||
"forms.tags": "Tags",
|
||||
"forms.tags.placeholder": "Wähle einen Tag",
|
||||
"forms.tags.info": "\"Abzeichen-Preis\" tag wird automatisch hinzugefügt (falls anwendbar)",
|
||||
"forms.tags.info": "\"Abzeichen-Preis\"-Tag wird automatisch hinzugefügt (falls anwendbar)",
|
||||
"forms.badges": "Abzeichen-Preis",
|
||||
"forms.badges.placeholder": "Wähle ein Abzeichen für das Event",
|
||||
"forms.mapPool": "Arenen-Pool",
|
||||
|
||||
"forms.participantCount": "Anzahl Teilnehmer",
|
||||
"forms.reportResultsHeader": "Berichten der Ergebnisse von {{eventName}}",
|
||||
"forms.reportResultsInfo": "Die Anzahl der eintragbaren Resultate ist frei wählbar. Es kann nur das erste Team sein, die Top 3 oder mehr.",
|
||||
"forms.reportResultsInfo": "Die Anzahl der eintragbaren Ergebnisse ist frei wählbar. Es kann nur das erste Team sein, die Top 3 oder mehr.",
|
||||
"forms.team.add": "Team hinzufügen",
|
||||
"forms.team.remove": "Team löschen",
|
||||
"forms.team.name": "Name des Teams",
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
"pages.faq": "FAQ",
|
||||
"pages.builds": "Ausrüstungen",
|
||||
"pages.analyzer": "Ausrüstungs-Analyse",
|
||||
"pages.maps": "Arenen-Listen",
|
||||
|
||||
"header.profile": "Profil",
|
||||
"header.logout": "Ausloggen",
|
||||
"header.login": "Einloggen",
|
||||
|
||||
"auth.errors.aborted": "Einloggen abgebrochen",
|
||||
"auth.errors.failed": "Einloggen fehlgeschlagen",
|
||||
"auth.errors.discordPermissions": "Für dein sendou.ink-Profil benötigt die Seite Zugriff auf den Namen, Avatar und verbundene Social-Media-Accounts in deinem Discord-Profil.",
|
||||
"auth.errors.unknown": "Das Einloggen via Discord ist aus unbekannten Gründen fehlgeschlagen. Falls dies wiederholt auftritt, kontaktiere uns bitte.",
|
||||
|
||||
"footer.github.subtitle": "Sourcecode",
|
||||
"footer.twitter.subtitle": "Updates",
|
||||
"footer.discord.subtitle": "Hilfe & Feedback",
|
||||
@@ -29,6 +35,11 @@
|
||||
"actions.loadMore": "Mehr laden",
|
||||
"actions.close": "Schließen",
|
||||
|
||||
"maps.createMapList": "Arenen-Liste erstellen",
|
||||
"maps.halfSz": "50% Herrschaft",
|
||||
"maps.mapPool": "Arenen-Pool",
|
||||
"maps.tournamentMaplist": "Arenen-Liste für Turnier erstellen (maps.iplabs.ink)",
|
||||
|
||||
"results": "Ergebnisse",
|
||||
|
||||
"forms.name": "Name",
|
||||
@@ -55,10 +66,5 @@
|
||||
"weapon.category.DUALIES": "Doppler",
|
||||
"weapon.category.BRELLAS": "Pluviatoren",
|
||||
"weapon.category.STRINGERS": "Stringer",
|
||||
"weapon.category.SPLATANAS": "Splatanas",
|
||||
|
||||
"auth.errors.aborted": "Login Abgebrochen",
|
||||
"auth.errors.failed": "Login Fehlgeschlagen",
|
||||
"auth.errors.discordPermissions": "Für dein sendou.ink Profil braucht die Seite die Erlaubnis deinen Discord-Namen, Avatar, und verbundene Social Accounts auszulesen.",
|
||||
"auth.errors.unknown": "Der Login via Discord ist aus unbekannten Gründen fehlgeschlagen. Falls dies wiederholt auftritt, kontaktiere uns bitte."
|
||||
"weapon.category.SPLATANAS": "Splatanas"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"plus.description": "Sieh vergangene Plus Server Abstimmungen und mehr",
|
||||
"badges.description": "Liste aller Abzeichen, die du für dein Profil verdienen kannst",
|
||||
"analyzer.description": "Analysiere, was deine Ausrüstungen wirklich bewirken",
|
||||
"maps.description": "Erstelle aus einem Arenen-Pool die Liste für dein Spiel",
|
||||
"recentWinners": "Aktuelle Gewinner",
|
||||
"upcomingEvents": "Bevorstehende Events",
|
||||
"articleBy": "von {{author}}"
|
||||
|
||||
@@ -10,5 +10,10 @@
|
||||
"STAGE_8": "Perlmutt-Akademie",
|
||||
"STAGE_9": "Störwerft",
|
||||
"STAGE_10": "Cetacea-Markt",
|
||||
"STAGE_11": "Flunder-Funpark"
|
||||
"STAGE_11": "Flunder-Funpark",
|
||||
"MODE_SHORT_TW": "RK",
|
||||
"MODE_SHORT_SZ": "HS",
|
||||
"MODE_SHORT_TC": "TK",
|
||||
"MODE_SHORT_RM": "OG",
|
||||
"MODE_SHORT_CB": "MC"
|
||||
}
|
||||
|
||||
@@ -80,31 +80,18 @@
|
||||
|
||||
**11/11**
|
||||
|
||||
### 🟡 calendar.json
|
||||
### 🟢 calendar.json
|
||||
|
||||
**44/46**
|
||||
|
||||
<details>
|
||||
<summary>Missing</summary>
|
||||
|
||||
- createMapList
|
||||
- forms.mapPool
|
||||
|
||||
</details>
|
||||
**46/46**
|
||||
|
||||
### 🟡 common.json
|
||||
|
||||
**54/60**
|
||||
**59/60**
|
||||
|
||||
<details>
|
||||
<summary>Missing</summary>
|
||||
|
||||
- pages.maps
|
||||
- actions.copyToClipboard
|
||||
- maps.createMapList
|
||||
- maps.halfSz
|
||||
- maps.mapPool
|
||||
- maps.tournamentMaplist
|
||||
|
||||
</details>
|
||||
|
||||
@@ -116,31 +103,13 @@
|
||||
|
||||
**6/6**
|
||||
|
||||
### 🟡 front.json
|
||||
### 🟢 front.json
|
||||
|
||||
**10/11**
|
||||
**11/11**
|
||||
|
||||
<details>
|
||||
<summary>Missing</summary>
|
||||
### 🟢 game-misc.json
|
||||
|
||||
- maps.description
|
||||
|
||||
</details>
|
||||
|
||||
### 🟡 game-misc.json
|
||||
|
||||
**12/17**
|
||||
|
||||
<details>
|
||||
<summary>Missing</summary>
|
||||
|
||||
- MODE_SHORT_TW
|
||||
- MODE_SHORT_SZ
|
||||
- MODE_SHORT_TC
|
||||
- MODE_SHORT_RM
|
||||
- MODE_SHORT_CB
|
||||
|
||||
</details>
|
||||
**17/17**
|
||||
|
||||
### 🟢 user.json
|
||||
|
||||
|
||||
Reference in New Issue
Block a user