Merge branch 'css-rework-sidenav' of https://github.com/sendou-ink/sendou.ink into css-rework-sidenav

This commit is contained in:
hfcRed 2026-03-14 16:31:04 +01:00
parent 445cb38b6c
commit a9d797f552
4 changed files with 84 additions and 5 deletions

View File

@ -3,12 +3,15 @@ import { ArrowLeft, MessageSquare, X } from "lucide-react";
import { Button } from "react-aria-components";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { resolveDatePlaceholders } from "~/features/chat/chat-utils";
import { Chat } from "~/features/chat/components/Chat";
import { useChatContext } from "~/features/chat/useChatContext";
import { useTimeFormat } from "~/hooks/useTimeFormat";
import sideNavStyles from "../SideNav.module.css";
import styles from "./ChatSidebar.module.css";
// xxx: add navIcon/url per room
export function ChatSidebar({ onClose }: { onClose?: () => void }) {
const chatContext = useChatContext();
@ -94,7 +97,14 @@ function RoomList({ onClose }: { onClose?: () => void }) {
styles.roomName,
)}
>
{room.header}
{resolveDatePlaceholders(room.header, (d) =>
formatDateTime(d, {
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
}),
)}
</span>
<span className={sideNavStyles.listLinkSubtitle}>
{room.subtitle}
@ -122,6 +132,7 @@ function RoomList({ onClose }: { onClose?: () => void }) {
function ChatView({ onClose }: { onClose?: () => void }) {
const chatContext = useChatContext()!;
const activeRoom = chatContext.activeRoom!;
const { formatDateTime } = useTimeFormat();
const otherRoomsUnreadCount = Object.entries(chatContext.unreadCounts)
.filter(([code]) => code !== activeRoom)
@ -147,7 +158,14 @@ function ChatView({ onClose }: { onClose?: () => void }) {
const headerContent = (
<>
<span className={styles.chatHeaderTitle}>
{room?.header ?? activeRoom}
{resolveDatePlaceholders(room?.header ?? activeRoom, (d) =>
formatDateTime(d, {
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
}),
)}
</span>
{room?.subtitle ? (
<span className={styles.chatHeaderSubtitle}>{room.subtitle}</span>

View File

@ -0,0 +1,44 @@
import { describe, expect, test } from "vitest";
import { datePlaceholder, resolveDatePlaceholders } from "./chat-utils";
describe("datePlaceholder", () => {
test("returns correctly formatted placeholder string", () => {
const date = new Date(1700000000000);
expect(datePlaceholder(date)).toBe("{{date:1700000000000}}");
});
});
describe("resolveDatePlaceholders", () => {
const mockFormat = (d: Date) => `FORMATTED:${d.getTime()}`;
test("replaces a single placeholder with formatted date", () => {
const text = "Starts at {{date:1700000000000}}";
expect(resolveDatePlaceholders(text, mockFormat)).toBe(
"Starts at FORMATTED:1700000000000",
);
});
test("replaces multiple placeholders in one string", () => {
const text = "From {{date:1700000000000}} to {{date:1700003600000}}";
expect(resolveDatePlaceholders(text, mockFormat)).toBe(
"From FORMATTED:1700000000000 to FORMATTED:1700003600000",
);
});
test("returns text unchanged when no placeholders present", () => {
const text = "Just a normal string";
expect(resolveDatePlaceholders(text, mockFormat)).toBe(text);
});
test("handles text that is only a placeholder", () => {
const text = "{{date:1700000000000}}";
expect(resolveDatePlaceholders(text, mockFormat)).toBe(
"FORMATTED:1700000000000",
);
});
});

View File

@ -1,5 +1,20 @@
import type { ChatMessage } from "./chat-types";
const DATE_PLACEHOLDER_PATTERN = /\{\{date:(\d+)\}\}/g;
export function datePlaceholder(date: Date): string {
return `{{date:${date.getTime()}}}`;
}
export function resolveDatePlaceholders(
text: string,
formatDateTime: (date: Date) => string,
): string {
return text.replace(DATE_PLACEHOLDER_PATTERN, (_match, ts) =>
formatDateTime(new Date(Number(ts))),
);
}
export function messageTypeToSound(type: ChatMessage["type"]) {
if (type === "LIKE_RECEIVED") return "sq_like";
if (type === "MATCH_STARTED") return "sq_match";

View File

@ -3,6 +3,7 @@ import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import { datePlaceholder } from "~/features/chat/chat-utils";
import { notify } from "~/features/notifications/core/notify.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { requirePermission } from "~/modules/permissions/guards.server";
@ -113,9 +114,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
if (fullPost?.chatCode) {
ChatSystemMessage.setMetadata({
chatCode: fullPost.chatCode,
header: "Scrim",
// xxx: better subtitle?
subtitle: `Scrim #${post.id}`,
header: datePlaceholder(
databaseTimestampToDate(request.at ?? post.at),
),
subtitle: "Scrim",
url: scrimPage(post.id),
participantUserIds: Scrim.participantIdsListFromAccepted(fullPost),
expiresAt: add(databaseTimestampToDate(request.at ?? post.at), {