diff --git a/common/zod/schemas.ts b/common/zod/schemas.ts
index 2c1c9cad..1c114305 100644
--- a/common/zod/schemas.ts
+++ b/common/zod/schemas.ts
@@ -123,8 +123,15 @@ export const SendMessageSchema = z.object({
content: z.string().min(1, { message: "Content must not be empty." }),
});
+export const MatchingStatusSchema = z.union([
+ z.literal("myRequest"),
+ z.literal("otherRequest"),
+ z.literal("matched"),
+]);
+
export const DMOverviewSchema = z.object({
isDM: z.literal(true),
+ matchingStatus: MatchingStatusSchema,
friendId: UserIDSchema,
name: NameSchema,
thumbnail: z.string(),
@@ -153,6 +160,7 @@ export const DMRoomSchema = z.object({
export const PersonalizedDMRoomSchema = z.object({
name: NameSchema,
thumbnail: z.string(),
+ matchingStatus: MatchingStatusSchema,
});
export const SharedRoomSchema = z.object({
diff --git a/server/src/database/chat.ts b/server/src/database/chat.ts
index 37f8a7cf..2a1e8307 100644
--- a/server/src/database/chat.ts
+++ b/server/src/database/chat.ts
@@ -14,9 +14,12 @@ import type {
} from "common/types";
import { prisma } from "./client";
import { getRelation } from "./matches";
-import { getMatchedUser } from "./requests";
+import {
+ getMatchedUser,
+ getPendingRequestsFromUser,
+ getPendingRequestsToUser,
+} from "./requests";
-// ユーザーの参加しているすべての Room の概要 (Overview) の取得
export async function getOverview(
user: UserID,
): Promise> {
@@ -24,7 +27,14 @@ export async function getOverview(
const matched = await getMatchedUser(user);
if (!matched.ok) return Err(matched.error);
- const dm = await Promise.all(
+ const senders = await getPendingRequestsToUser(user);
+ if (!senders.ok) return Err(senders.error);
+
+ const receivers = await getPendingRequestsFromUser(user);
+ if (!receivers.ok) return Err(receivers.error);
+
+ //マッチングしている人のオーバービュー
+ const matchingOverview = await Promise.all(
matched.value.map(async (friend) => {
const lastMessageResult = await getLastMessage(user, friend.id);
const lastMessage = lastMessageResult.ok
@@ -32,6 +42,7 @@ export async function getOverview(
: undefined;
const overview: DMOverview = {
isDM: true,
+ matchingStatus: "matched",
friendId: friend.id,
name: friend.name,
thumbnail: friend.pictureUrl,
@@ -41,6 +52,44 @@ export async function getOverview(
}),
);
+ //自分にリクエストを送ってきた人のオーバービュー
+ const senderOverview = await Promise.all(
+ senders.value.map(async (sender) => {
+ const lastMessageResult = await getLastMessage(user, sender.id);
+ const lastMessage = lastMessageResult.ok
+ ? lastMessageResult.value
+ : undefined;
+ const overview: DMOverview = {
+ isDM: true,
+ matchingStatus: "otherRequest",
+ friendId: sender.id,
+ name: sender.name,
+ thumbnail: sender.pictureUrl,
+ lastMsg: lastMessage,
+ };
+ return overview;
+ }),
+ );
+
+ //自分がリクエストを送った人のオーバービュー
+ const receiverOverview = await Promise.all(
+ receivers.value.map(async (receiver) => {
+ const lastMessageResult = await getLastMessage(user, receiver.id);
+ const lastMessage = lastMessageResult.ok
+ ? lastMessageResult.value
+ : undefined;
+ const overview: DMOverview = {
+ isDM: true,
+ matchingStatus: "myRequest",
+ friendId: receiver.id,
+ name: receiver.name,
+ thumbnail: receiver.pictureUrl,
+ lastMsg: lastMessage,
+ };
+ return overview;
+ }),
+ );
+
const sharedRooms: {
id: number;
name: string;
@@ -61,7 +110,13 @@ export async function getOverview(
};
return overview;
});
- return Ok([...shared, ...dm]);
+
+ return Ok([
+ ...matchingOverview,
+ ...senderOverview,
+ ...receiverOverview,
+ ...shared,
+ ]);
} catch (e) {
return Err(e);
}
diff --git a/server/src/functions/chat.ts b/server/src/functions/chat.ts
index 1277e54d..1e748918 100644
--- a/server/src/functions/chat.ts
+++ b/server/src/functions/chat.ts
@@ -42,8 +42,10 @@ export async function sendDM(
send: SendMessage,
): Promise> {
const rel = await getRelation(from, to);
- if (!rel.ok || rel.value.status !== "MATCHED")
- return http.forbidden("cannot send to non-friend");
+ if (!rel.ok || rel.value.status === "REJECTED")
+ return http.forbidden(
+ "You cannot send a message because the friendship request was rejected.",
+ );
// they are now MATCHED
const msg: Omit = {
@@ -59,21 +61,28 @@ export async function sendDM(
}
export async function getDM(
- requester: UserID,
- _with: UserID,
+ user: UserID,
+ friend: UserID,
): Promise> {
- if (!areMatched(requester, _with))
- return http.forbidden("cannot DM with a non-friend");
+ const rel = await getRelation(user, friend);
+ if (!rel.ok || rel.value.status === "REJECTED")
+ return http.forbidden("cannot send to rejected-friend");
- const room = await db.getDMbetween(requester, _with);
+ const room = await db.getDMbetween(user, friend);
if (!room.ok) return http.internalError();
- const friendData = await getUserByID(_with);
+ const friendData = await getUserByID(friend);
if (!friendData.ok) return http.notFound("friend not found");
const personalized: PersonalizedDMRoom & DMRoom = {
name: friendData.value.name,
thumbnail: friendData.value.pictureUrl,
+ matchingStatus:
+ rel.value.status === "MATCHED"
+ ? "matched"
+ : rel.value.sendingUserId === user //どっちが送ったリクエストなのかを判定
+ ? "myRequest"
+ : "otherRequest",
...room.value,
};
diff --git a/server/src/router/chat.ts b/server/src/router/chat.ts
index 2f4c11de..a6052e1f 100644
--- a/server/src/router/chat.ts
+++ b/server/src/router/chat.ts
@@ -23,12 +23,12 @@ router.get("/overview", async (req, res) => {
res.status(result.code).send(result.body);
});
-// send DM to userid.
+// send DM to userId.
router.post("/dm/to/:userid", async (req, res) => {
const user = await safeGetUserId(req);
if (!user.ok) return res.status(401).send("auth error");
const friend = safeParseInt(req.params.userid);
- if (!friend.ok) return res.status(400).send("bad param encoding: `userid`");
+ if (!friend.ok) return res.status(400).send("bad param encoding: `userId`");
const send = SendMessageSchema.safeParse(req.body);
if (!send.success) {
@@ -42,14 +42,14 @@ router.post("/dm/to/:userid", async (req, res) => {
res.status(result.code).send(result.body);
});
-// GET a DM Room with userid, CREATE one if not found.
+// GET a DM Room with userId, CREATE one if not found.
router.get("/dm/with/:userid", async (req, res) => {
const user = await safeGetUserId(req);
if (!user.ok) return res.status(401).send("auth error");
const friend = safeParseInt(req.params.userid);
if (!friend.ok)
- return res.status(400).send("invalid param `userid` formatting");
+ return res.status(400).send("invalid param `userId` formatting");
const result = await core.getDM(user.value, friend.value);
diff --git a/web/api/chat/chat.ts b/web/api/chat/chat.ts
index 63788ea5..83e307d5 100644
--- a/web/api/chat/chat.ts
+++ b/web/api/chat/chat.ts
@@ -3,6 +3,7 @@ import type {
InitRoom,
Message,
MessageID,
+ PersonalizedDMRoom,
RoomOverview,
SendMessage,
ShareRoomID,
@@ -83,22 +84,16 @@ export async function sendDM(
return res.json();
}
-export async function getDM(friendId: UserID): Promise<
- DMRoom & {
- name: string;
- thumbnail: string;
- }
-> {
+export async function getDM(
+ friendId: UserID,
+): Promise {
const res = await credFetch("GET", endpoints.dmWith(friendId));
if (res.status === 401) throw new ErrUnauthorized();
if (res.status !== 200)
throw new Error(
`getDM() failed: expected status code 200, got ${res.status}`,
);
- const json: DMRoom & {
- name: string;
- thumbnail: string;
- } = await res.json();
+ const json: DMRoom & PersonalizedDMRoom = await res.json();
if (!Array.isArray(json?.messages)) return json;
for (const m of json.messages) {
m.createdAt = new Date(m.createdAt);
diff --git a/web/app/chat/[id]/page.tsx b/web/app/chat/[id]/page.tsx
index 1f717305..6db7a216 100644
--- a/web/app/chat/[id]/page.tsx
+++ b/web/app/chat/[id]/page.tsx
@@ -1,27 +1,13 @@
"use client";
+import type { DMRoom, PersonalizedDMRoom } from "common/types";
+import Link from "next/link";
import { useEffect, useState } from "react";
import * as chat from "~/api/chat/chat";
import { RoomWindow } from "~/components/chat/RoomWindow";
export default function Page({ params }: { params: { id: string } }) {
const id = Number.parseInt(params.id);
- const [room, setRoom] = useState<
- | ({
- id: number;
- isDM: true;
- messages: {
- id: number;
- creator: number;
- createdAt: Date;
- content: string;
- edited: boolean;
- }[];
- } & {
- name: string;
- thumbnail: string;
- })
- | null
- >(null);
+ const [room, setRoom] = useState<(DMRoom & PersonalizedDMRoom) | null>(null);
useEffect(() => {
(async () => {
const room = await chat.getDM(id);
@@ -31,8 +17,16 @@ export default function Page({ params }: { params: { id: string } }) {
return (
<>
- idは{id}です。
- {room ? : データないよ
}
+ {room ? (
+
+ ) : (
+
+ Sorry, an unexpected error has occurred.
+
+ Go Back
+
+
+ )}
>
);
}
diff --git a/web/components/chat/RoomList.tsx b/web/components/chat/RoomList.tsx
index 1c6b6fad..710feb3c 100644
--- a/web/components/chat/RoomList.tsx
+++ b/web/components/chat/RoomList.tsx
@@ -34,6 +34,49 @@ export function RoomList(props: RoomListProps) {
{roomsData?.map((room) => {
if (room.isDM) {
+ if (room.matchingStatus === "otherRequest") {
+ return (
+ {
+ e.stopPropagation();
+ navigateToRoom(room);
+ }}
+ >
+
+
+ );
+ }
+ if (room.matchingStatus === "myRequest") {
+ return (
+ {
+ e.stopPropagation();
+ navigateToRoom(room);
+ }}
+ >
+
+
+ );
+ }
+ // if (room.matchingStatus === "matched")
return (
+ {room.matchingStatus !== "matched" && (
+
+ )}
+
@@ -261,3 +256,57 @@ export function RoomWindow(props: Props) {
>
);
}
+
+type FloatingMessageProps = {
+ message: string;
+ friendId: number;
+ showButtons: boolean;
+};
+
+const FloatingMessage = ({
+ message,
+ friendId,
+ showButtons,
+}: FloatingMessageProps) => {
+ const router = useRouter();
+
+ return (
+
+
+
{message}
+ {showButtons && (
+
+ {/* biome-ignore lint/a11y/useButtonType: */}
+
+ {/* biome-ignore lint/a11y/useButtonType: */}
+
+
+ )}
+
+
+ );
+};
diff --git a/web/components/human/humanListItem.tsx b/web/components/human/humanListItem.tsx
index b1a4a575..2fe31ebb 100644
--- a/web/components/human/humanListItem.tsx
+++ b/web/components/human/humanListItem.tsx
@@ -7,6 +7,7 @@ type HumanListItemProps = {
pictureUrl: string;
lastMessage?: string;
rollUpName?: boolean; // is currently only intended to be used in Chat
+ statusMessage?: string;
onDelete?: (id: number) => void;
onOpen?: (user: { id: number; name: string; pictureUrl: string }) => void;
onAccept?: (id: number) => void;
@@ -23,6 +24,7 @@ export function HumanListItem(props: HumanListItemProps) {
pictureUrl,
rollUpName,
lastMessage,
+ statusMessage,
onDelete,
onOpen,
onAccept,
@@ -61,6 +63,9 @@ export function HumanListItem(props: HumanListItemProps) {
{lastMessage}
)}
+ {statusMessage && (
+ {statusMessage}
+ )}
@@ -68,14 +73,23 @@ export function HumanListItem(props: HumanListItemProps) {
// biome-ignore lint/a11y/useButtonType:
)}
{onReject && (
// biome-ignore lint/a11y/useButtonType:
-