mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-27 04:54:25 -05:00
/events page
This commit is contained in:
8
app/components/EventsList.module.css
Normal file
8
app/components/EventsList.module.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.dayHeader {
|
||||
padding: var(--s-2) var(--s-2) var(--s-1);
|
||||
font-size: var(--font-2xs);
|
||||
font-weight: var(--weight-bold);
|
||||
color: var(--color-text-high);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
101
app/components/EventsList.tsx
Normal file
101
app/components/EventsList.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { isToday, isTomorrow } from "date-fns";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { SidebarEvent } from "~/features/sidebar/core/sidebar.server";
|
||||
import styles from "./EventsList.module.css";
|
||||
import { ListLink } from "./SideNav";
|
||||
|
||||
export function EventsList({
|
||||
events,
|
||||
onClick,
|
||||
}: {
|
||||
events: SidebarEvent[];
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation(["front"]);
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<div className="text-lighter text-sm p-2">
|
||||
{t("front:sideNav.noEvents")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getDayKey = (timestamp: number) => {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toDateString();
|
||||
};
|
||||
|
||||
const formatDayHeader = (date: Date) => {
|
||||
if (isToday(date)) {
|
||||
const rtf = new Intl.RelativeTimeFormat(i18n.language, {
|
||||
numeric: "auto",
|
||||
});
|
||||
const str = rtf.format(0, "day");
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
if (isTomorrow(date)) {
|
||||
const rtf = new Intl.RelativeTimeFormat(i18n.language, {
|
||||
numeric: "auto",
|
||||
});
|
||||
const str = rtf.format(1, "day");
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
return date.toLocaleDateString(i18n.language, {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
return date.toLocaleTimeString(i18n.language, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const groupedEvents = events.reduce<Record<string, typeof events>>(
|
||||
(acc, event) => {
|
||||
const key = getDayKey(event.startTime);
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(event);
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const dayKeys = Object.keys(groupedEvents);
|
||||
|
||||
return (
|
||||
<>
|
||||
{dayKeys.map((dayKey) => {
|
||||
const dayEvents = groupedEvents[dayKey];
|
||||
const firstDate = new Date(dayEvents[0].startTime * 1000);
|
||||
|
||||
return (
|
||||
<div key={dayKey}>
|
||||
<div className={styles.dayHeader}>{formatDayHeader(firstDate)}</div>
|
||||
{dayEvents.map((event) => (
|
||||
<ListLink
|
||||
key={`${event.type}-${event.id}`}
|
||||
to={event.url}
|
||||
imageUrl={event.logoUrl ?? undefined}
|
||||
subtitle={formatTime(new Date(event.startTime * 1000))}
|
||||
onClick={onClick}
|
||||
>
|
||||
{event.scrimStatus === "booked"
|
||||
? t("front:sideNav.scrimVs", { opponent: event.name })
|
||||
: event.scrimStatus === "looking"
|
||||
? t("front:sideNav.lookingForScrim")
|
||||
: event.name}
|
||||
</ListLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -299,24 +299,24 @@
|
||||
}
|
||||
|
||||
.panelSectionLink {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: fit-content;
|
||||
margin: var(--s-4) auto;
|
||||
font-size: var(--font-2xs);
|
||||
color: var(--color-text-high);
|
||||
text-decoration: none;
|
||||
padding: var(--s-3) var(--s-2);
|
||||
text-align: center;
|
||||
padding: var(--s-1) var(--s-3);
|
||||
background-color: var(--color-bg-higher);
|
||||
border-radius: var(--radius-field);
|
||||
|
||||
& svg {
|
||||
stroke-width: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.panelSectionLink:hover {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dayHeader {
|
||||
padding: var(--s-2) var(--s-2) var(--s-1);
|
||||
font-size: var(--font-2xs);
|
||||
font-weight: var(--weight-bold);
|
||||
color: var(--color-text-high);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
background-color: var(--color-bg-high);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { isToday, isTomorrow } from "date-fns";
|
||||
import {
|
||||
Calendar,
|
||||
ChevronRight,
|
||||
LogIn,
|
||||
Menu,
|
||||
Settings,
|
||||
@@ -18,12 +18,14 @@ import { useUser } from "~/features/auth/core/user";
|
||||
import { FriendMenu } from "~/features/friends/components/FriendMenu";
|
||||
import type { RootLoaderData } from "~/root";
|
||||
import {
|
||||
EVENTS_PAGE,
|
||||
FRIENDS_PAGE,
|
||||
navIconUrl,
|
||||
SETTINGS_PAGE,
|
||||
userPage,
|
||||
} from "~/utils/urls";
|
||||
import { Avatar } from "./Avatar";
|
||||
import { EventsList } from "./EventsList";
|
||||
import { SendouButton } from "./elements/Button";
|
||||
import { Image } from "./Image";
|
||||
import { LogInButtonContainer } from "./layout/LogInButtonContainer";
|
||||
@@ -34,7 +36,6 @@ import {
|
||||
import { navItems } from "./layout/nav-items";
|
||||
import styles from "./MobileNav.module.css";
|
||||
import { NotificationDot } from "./NotificationDot";
|
||||
import { ListLink } from "./SideNav";
|
||||
import { StreamListItems } from "./StreamListItems";
|
||||
|
||||
type SidebarData = RootLoaderData["sidebar"] | undefined;
|
||||
@@ -302,6 +303,7 @@ function FriendsPanel({
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:actions.viewAll")}
|
||||
<ChevronRight size={14} />
|
||||
</Link>
|
||||
</MobilePanel>
|
||||
);
|
||||
@@ -314,91 +316,19 @@ function TourneysPanel({
|
||||
events: NonNullable<SidebarData>["events"];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t, i18n } = useTranslation(["front"]);
|
||||
|
||||
const formatDayHeader = (date: Date) => {
|
||||
if (isToday(date)) {
|
||||
const rtf = new Intl.RelativeTimeFormat(i18n.language, {
|
||||
numeric: "auto",
|
||||
});
|
||||
const str = rtf.format(0, "day");
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
if (isTomorrow(date)) {
|
||||
const rtf = new Intl.RelativeTimeFormat(i18n.language, {
|
||||
numeric: "auto",
|
||||
});
|
||||
const str = rtf.format(1, "day");
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
return date.toLocaleDateString(i18n.language, {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
return date.toLocaleTimeString(i18n.language, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const getDayKey = (timestamp: number) => {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toDateString();
|
||||
};
|
||||
|
||||
const groupedEvents = events.reduce<Record<string, typeof events>>(
|
||||
(acc, event) => {
|
||||
const key = getDayKey(event.startTime);
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(event);
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const dayKeys = Object.keys(groupedEvents);
|
||||
const { t } = useTranslation(["front", "common"]);
|
||||
|
||||
return (
|
||||
<MobilePanel title={t("front:sideNav.myCalendar")} onClose={onClose}>
|
||||
{events.length > 0 ? (
|
||||
dayKeys.map((dayKey) => {
|
||||
const dayEvents = groupedEvents[dayKey];
|
||||
const firstDate = new Date(dayEvents[0].startTime * 1000);
|
||||
|
||||
return (
|
||||
<div key={dayKey}>
|
||||
<div className={styles.dayHeader}>
|
||||
{formatDayHeader(firstDate)}
|
||||
</div>
|
||||
{dayEvents.map((event) => (
|
||||
<ListLink
|
||||
key={`${event.type}-${event.id}`}
|
||||
to={event.url}
|
||||
imageUrl={event.logoUrl ?? undefined}
|
||||
subtitle={formatTime(new Date(event.startTime * 1000))}
|
||||
onClick={onClose}
|
||||
>
|
||||
{event.scrimStatus === "booked"
|
||||
? t("front:sideNav.scrimVs", { opponent: event.name })
|
||||
: event.scrimStatus === "looking"
|
||||
? t("front:sideNav.lookingForScrim")
|
||||
: event.name}
|
||||
</ListLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="text-lighter text-sm p-2">
|
||||
{t("front:sideNav.noEvents")}
|
||||
</div>
|
||||
)}
|
||||
<EventsList events={events} onClick={onClose} />
|
||||
<Link
|
||||
to={EVENTS_PAGE}
|
||||
className={styles.panelSectionLink}
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:actions.viewAll")}
|
||||
<ChevronRight size={14} />
|
||||
</Link>
|
||||
</MobilePanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,3 +44,32 @@
|
||||
.divider {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.viewAllLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: fit-content;
|
||||
margin: var(--s-2) auto;
|
||||
font-size: var(--font-2xs);
|
||||
color: var(--color-text-high);
|
||||
text-decoration: none;
|
||||
padding: var(--s-1) var(--s-3);
|
||||
background-color: var(--color-bg-higher);
|
||||
border-radius: var(--radius-field);
|
||||
|
||||
& svg {
|
||||
stroke-width: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 599px) {
|
||||
.viewAllLink {
|
||||
margin-top: var(--s-4);
|
||||
}
|
||||
}
|
||||
|
||||
.viewAllLink:hover {
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg-high);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Bell, RefreshCcw } from "lucide-react";
|
||||
import { Bell, ChevronRight, RefreshCcw } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMatches, useRevalidator } from "react-router";
|
||||
import { Link, useMatches, useRevalidator } from "react-router";
|
||||
import {
|
||||
NotificationItem,
|
||||
NotificationItemDivider,
|
||||
@@ -11,7 +11,7 @@ import { NOTIFICATIONS } from "~/features/notifications/notifications-contants";
|
||||
import type { RootLoaderData } from "~/root";
|
||||
import { NOTIFICATIONS_URL } from "~/utils/urls";
|
||||
import { useMarkNotificationsAsSeen } from "../../features/notifications/notifications-hooks";
|
||||
import { LinkButton, SendouButton } from "../elements/Button";
|
||||
import { SendouButton } from "../elements/Button";
|
||||
|
||||
import styles from "./NotificationPopover.module.css";
|
||||
|
||||
@@ -95,15 +95,15 @@ function NotificationsFooter({ onClose }: { onClose?: () => void }) {
|
||||
return (
|
||||
<div>
|
||||
<hr className={styles.divider} />
|
||||
<LinkButton
|
||||
variant="minimal"
|
||||
size="small"
|
||||
<Link
|
||||
to={NOTIFICATIONS_URL}
|
||||
testId="notifications-see-all-button"
|
||||
className={styles.viewAllLink}
|
||||
data-testid="notifications-see-all-button"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:notifications.seeAll")}
|
||||
</LinkButton>
|
||||
{t("common:actions.viewAll")}
|
||||
<ChevronRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,12 @@ import { useUser } from "~/features/auth/core/user";
|
||||
import { FriendMenu } from "~/features/friends/components/FriendMenu";
|
||||
import type { RootLoaderData } from "~/root";
|
||||
import type { Breadcrumb, SendouRouteHandle } from "~/utils/remix.server";
|
||||
import { FRIENDS_PAGE, SETTINGS_PAGE, userPage } from "~/utils/urls";
|
||||
import {
|
||||
EVENTS_PAGE,
|
||||
FRIENDS_PAGE,
|
||||
SETTINGS_PAGE,
|
||||
userPage,
|
||||
} from "~/utils/urls";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { SendouButton } from "../elements/Button";
|
||||
import { SendouPopover } from "../elements/Popover";
|
||||
@@ -210,7 +215,17 @@ export function Layout({
|
||||
|
||||
const sideNavChildren = (
|
||||
<>
|
||||
<SideNavHeader icon={<Calendar />}>
|
||||
<SideNavHeader
|
||||
icon={<Calendar />}
|
||||
action={
|
||||
user ? (
|
||||
<Link to={EVENTS_PAGE} className={styles.viewAllLink}>
|
||||
{t("common:actions.viewAll")}
|
||||
<ChevronRight size={14} />
|
||||
</Link>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{t("front:sideNav.myCalendar")}
|
||||
</SideNavHeader>
|
||||
{events.length > 0 ? (
|
||||
|
||||
32
app/features/calendar/loaders/events.server.ts
Normal file
32
app/features/calendar/loaders/events.server.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
|
||||
import * as ScrimPostRepository from "~/features/scrims/ScrimPostRepository.server";
|
||||
import {
|
||||
scrimToSidebarEvent,
|
||||
tournamentToSidebarEvent,
|
||||
} from "~/features/sidebar/core/sidebar.server";
|
||||
|
||||
export type EventsLoaderData = typeof loader;
|
||||
|
||||
export const loader = async () => {
|
||||
const user = requireUser();
|
||||
|
||||
const [tournamentsData, scrimsData] = await Promise.all([
|
||||
ShowcaseTournaments.frontPageTournamentsByUserId(user.id),
|
||||
ScrimPostRepository.findUserScrims(user.id),
|
||||
]);
|
||||
|
||||
const registered = tournamentsData.participatingFor
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
|
||||
const hosting = tournamentsData.organizingFor
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
|
||||
const scrims = scrimsData
|
||||
.map(scrimToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
|
||||
return { registered, hosting, scrims };
|
||||
};
|
||||
22
app/features/calendar/routes/events.module.css
Normal file
22
app/features/calendar/routes/events.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.eventsListHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--s-2);
|
||||
margin-block-end: var(--s-2);
|
||||
}
|
||||
|
||||
.filterRadio {
|
||||
padding: var(--s-1) var(--s-2);
|
||||
border-radius: var(--radius-field);
|
||||
cursor: pointer;
|
||||
color: var(--color-text-high);
|
||||
font-weight: var(--weight-semi);
|
||||
font-size: var(--font-3xs);
|
||||
}
|
||||
|
||||
.filterRadioSelected {
|
||||
background-color: var(--color-bg-higher);
|
||||
color: var(--color-text);
|
||||
}
|
||||
83
app/features/calendar/routes/events.tsx
Normal file
83
app/features/calendar/routes/events.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import { Radio, RadioGroup } from "react-aria-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLoaderData } from "react-router";
|
||||
import { EventsList } from "~/components/EventsList";
|
||||
import { Main } from "~/components/Main";
|
||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||
import { CALENDAR_PAGE } from "~/utils/urls";
|
||||
import type { EventsLoaderData } from "../loaders/events.server";
|
||||
import styles from "./events.module.css";
|
||||
|
||||
export { loader } from "../loaders/events.server";
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: ["calendar"],
|
||||
};
|
||||
|
||||
type ViewFilter = "registered" | "hosting" | "scrims";
|
||||
|
||||
export default function EventsPage() {
|
||||
const { t } = useTranslation(["calendar"]);
|
||||
const data = useLoaderData<EventsLoaderData>();
|
||||
const [filter, setFilter] = useState<ViewFilter>("registered");
|
||||
|
||||
const viewLabels: Record<ViewFilter, string> = {
|
||||
registered: t("calendar:events.view.registered"),
|
||||
hosting: t("calendar:events.view.hosting"),
|
||||
scrims: t("calendar:events.view.scrims"),
|
||||
};
|
||||
|
||||
const shownEvents =
|
||||
filter === "registered"
|
||||
? data.registered
|
||||
: filter === "hosting"
|
||||
? data.hosting
|
||||
: data.scrims;
|
||||
|
||||
const hasNoEventsAtAll =
|
||||
data.registered.length === 0 &&
|
||||
data.hosting.length === 0 &&
|
||||
data.scrims.length === 0;
|
||||
|
||||
return (
|
||||
<Main halfWidth>
|
||||
<div className={styles.eventsListHeader}>
|
||||
<h2 className="text-lg mx-2">{t("calendar:events.title")}</h2>
|
||||
{hasNoEventsAtAll ? null : (
|
||||
<RadioGroup
|
||||
value={filter}
|
||||
onChange={(v) => setFilter(v as ViewFilter)}
|
||||
orientation="horizontal"
|
||||
className="stack horizontal xs"
|
||||
>
|
||||
{(["registered", "hosting", "scrims"] as const).map((value) => (
|
||||
<Radio key={value} value={value}>
|
||||
{({ isSelected }) => (
|
||||
<span
|
||||
className={clsx(styles.filterRadio, {
|
||||
[styles.filterRadioSelected]: isSelected,
|
||||
})}
|
||||
>
|
||||
{viewLabels[value]}
|
||||
</span>
|
||||
)}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
</div>
|
||||
{hasNoEventsAtAll ? (
|
||||
<p className="text-lighter text-sm">
|
||||
{t("calendar:events.emptyAll")}{" "}
|
||||
<Link to={CALENDAR_PAGE}>{t("calendar:events.findOnCalendar")}</Link>
|
||||
</p>
|
||||
) : shownEvents.length === 0 ? (
|
||||
<p className="text-lighter text-sm">{t("calendar:events.empty")}</p>
|
||||
) : (
|
||||
<EventsList events={shownEvents} />
|
||||
)}
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import * as FriendRepository from "~/features/friends/FriendRepository.server";
|
||||
import { resolveFriendActivity } from "~/features/friends/friends-utils.server";
|
||||
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
|
||||
import * as LiveStreamRepository from "~/features/live-streams/LiveStreamRepository.server";
|
||||
import type { SidebarScrim } from "~/features/scrims/ScrimPostRepository.server";
|
||||
import * as ScrimPostRepository from "~/features/scrims/ScrimPostRepository.server";
|
||||
import { getSendouQSidebarStreams } from "~/features/sendouq-streams/core/streams.server";
|
||||
import type { TournamentTierNumber } from "~/features/tournament/core/tiering";
|
||||
@@ -81,27 +82,9 @@ export async function resolveSidebarData(userId: number | null) {
|
||||
seenTournamentIds.add(t.id);
|
||||
return true;
|
||||
})
|
||||
.map((t) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
url: t.url,
|
||||
logoUrl: t.logoUrl,
|
||||
startTime: t.startTime,
|
||||
type: "tournament" as const,
|
||||
}));
|
||||
.map(tournamentToSidebarEvent);
|
||||
|
||||
const scrimsIconUrl = `${navIconUrl("scrims")}.png`;
|
||||
const scrimEvents: SidebarEvent[] = scrimsData.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.opponentName ?? "Scrim",
|
||||
url: s.isAccepted
|
||||
? href("/scrims/:id", { id: String(s.id) })
|
||||
: href("/scrims"),
|
||||
logoUrl: s.opponentAvatarUrl ?? scrimsIconUrl,
|
||||
startTime: s.at,
|
||||
type: "scrim" as const,
|
||||
scrimStatus: s.isAccepted ? ("booked" as const) : ("looking" as const),
|
||||
}));
|
||||
const scrimEvents: SidebarEvent[] = scrimsData.map(scrimToSidebarEvent);
|
||||
|
||||
const personalEvents = [...tournamentEvents, ...scrimEvents].sort(
|
||||
(a, b) => a.startTime - b.startTime,
|
||||
@@ -347,3 +330,32 @@ function rowToSidebarFriend(
|
||||
tournamentId: row.tournamentId,
|
||||
};
|
||||
}
|
||||
|
||||
export function tournamentToSidebarEvent(
|
||||
t: ShowcaseCalendarEvent,
|
||||
): SidebarEvent {
|
||||
return {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
url: t.url,
|
||||
logoUrl: t.logoUrl,
|
||||
startTime: t.startTime,
|
||||
type: "tournament" as const,
|
||||
};
|
||||
}
|
||||
|
||||
const SCRIMS_ICON_URL = `${navIconUrl("scrims")}.png`;
|
||||
|
||||
export function scrimToSidebarEvent(s: SidebarScrim): SidebarEvent {
|
||||
return {
|
||||
id: s.id,
|
||||
name: s.opponentName ?? "Scrim",
|
||||
url: s.isAccepted
|
||||
? href("/scrims/:id", { id: String(s.id) })
|
||||
: href("/scrims"),
|
||||
logoUrl: s.opponentAvatarUrl ?? SCRIMS_ICON_URL,
|
||||
startTime: s.at,
|
||||
type: "scrim" as const,
|
||||
scrimStatus: s.isAccepted ? ("booked" as const) : ("looking" as const),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ export default [
|
||||
|
||||
route("/friends", "features/friends/routes/friends.tsx"),
|
||||
|
||||
route("/events", "features/calendar/routes/events.tsx"),
|
||||
|
||||
route("/suspended", "features/ban/routes/suspended.tsx"),
|
||||
|
||||
route("/u", "features/user-search/routes/u.tsx"),
|
||||
|
||||
@@ -120,6 +120,7 @@ export const SENDOUQ_STREAMS_PAGE = "/q/streams";
|
||||
export const TIERS_PAGE = "/tiers";
|
||||
export const SUSPENDED_PAGE = "/suspended";
|
||||
export const LFG_PAGE = "/lfg";
|
||||
export const EVENTS_PAGE = "/events";
|
||||
export const FRIENDS_PAGE = "/friends";
|
||||
export const SETTINGS_PAGE = "/settings";
|
||||
export const LUTI_PAGE = "/luti";
|
||||
|
||||
@@ -79,5 +79,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -79,5 +79,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -79,5 +79,12 @@
|
||||
"filter.applyAndDefault": "Apply & make default",
|
||||
"forms.draft": "Draft",
|
||||
"forms.draftInfo": "Draft tournaments are hidden and only visible to organizers. The tournament must be opened (by disabling this toggle) before any bracket can be started.",
|
||||
"forms.draftBracketStartBlocked": "Tournament is in draft mode. Edit the tournament and disable the draft toggle before starting the bracket."
|
||||
"forms.draftBracketStartBlocked": "Tournament is in draft mode. Edit the tournament and disable the draft toggle before starting the bracket.",
|
||||
"events.title": "My Events",
|
||||
"events.view.registered": "Registered",
|
||||
"events.view.hosting": "Hosting",
|
||||
"events.view.scrims": "Scrims",
|
||||
"events.empty": "No events in this category",
|
||||
"events.emptyAll": "You have no upcoming events.",
|
||||
"events.findOnCalendar": "Find an event to join on the calendar!"
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "החל והפוך לברירת מחדל",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -75,5 +75,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -75,5 +75,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -79,5 +79,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -83,5 +83,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -81,5 +81,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -83,5 +83,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
@@ -75,5 +75,12 @@
|
||||
"filter.applyAndDefault": "",
|
||||
"forms.draft": "",
|
||||
"forms.draftInfo": "",
|
||||
"forms.draftBracketStartBlocked": ""
|
||||
"forms.draftBracketStartBlocked": "",
|
||||
"events.title": "",
|
||||
"events.view.registered": "",
|
||||
"events.view.hosting": "",
|
||||
"events.view.scrims": "",
|
||||
"events.empty": "",
|
||||
"events.emptyAll": "",
|
||||
"events.findOnCalendar": ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user