mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 15:48:23 -05:00
UserCard: when friend request pending from another make button accept request instead of showing as pending
This commit is contained in:
@@ -187,7 +187,7 @@ export async function findFriendRequestBetween({
|
||||
}) {
|
||||
return db
|
||||
.selectFrom("FriendRequest")
|
||||
.select("FriendRequest.id")
|
||||
.select(["FriendRequest.id", "FriendRequest.senderId"])
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb.and([
|
||||
|
||||
@@ -253,7 +253,8 @@ function CardContent({
|
||||
{friendship && !friendship.isFriend ? (
|
||||
<FriendRequestButton
|
||||
targetUserId={data.id}
|
||||
hasPendingFriendRequest={friendship.hasPendingFriendRequest}
|
||||
sentFriendRequest={friendship.sentFriendRequest}
|
||||
incomingFriendRequestId={friendship.incomingFriendRequestId}
|
||||
/>
|
||||
) : null}
|
||||
<SendouButton
|
||||
@@ -369,42 +370,69 @@ function NoteView({
|
||||
}
|
||||
|
||||
/**
|
||||
* Send friend request action on the card. Submits to the `/friends` route action and shows a
|
||||
* checkmark once a request is pending (server-known or just sent). Cancelling a pending request
|
||||
* is done on the `/friends` page.
|
||||
* Friend request action on the card, submitting to the `/friends` route action. Normally sends a
|
||||
* request and shows a checkmark once one is pending (server-known or just sent); when the shown
|
||||
* user has already sent the viewer a request, the same add-friend press accepts it instead.
|
||||
* Cancelling a pending request is done on the `/friends` page.
|
||||
*/
|
||||
function FriendRequestButton({
|
||||
targetUserId,
|
||||
hasPendingFriendRequest,
|
||||
sentFriendRequest,
|
||||
incomingFriendRequestId,
|
||||
}: {
|
||||
targetUserId: number;
|
||||
hasPendingFriendRequest: boolean;
|
||||
sentFriendRequest: boolean;
|
||||
incomingFriendRequestId: number | null;
|
||||
}) {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const fetcher = useFetcher();
|
||||
const previousStateRef = React.useRef(fetcher.state);
|
||||
const acceptsIncomingRequest = incomingFriendRequestId !== null;
|
||||
|
||||
// fires on the submitting -> revalidating transition (not on idle) so the accepted toast still
|
||||
// shows before the revalidated friendship data unmounts this button
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
previousStateRef.current !== "idle" &&
|
||||
fetcher.state === "idle" &&
|
||||
previousStateRef.current === "submitting" &&
|
||||
fetcher.state !== "submitting" &&
|
||||
fetcher.data === null
|
||||
) {
|
||||
toastQueue.add(
|
||||
{
|
||||
message: t("user:card.friendRequestSent"),
|
||||
message: acceptsIncomingRequest
|
||||
? "Friend request accepted"
|
||||
: t("user:card.friendRequestSent"),
|
||||
variant: "success",
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
}
|
||||
previousStateRef.current = fetcher.state;
|
||||
}, [fetcher.state, fetcher.data, t]);
|
||||
}, [fetcher.state, fetcher.data, acceptsIncomingRequest, t]);
|
||||
|
||||
if (acceptsIncomingRequest) {
|
||||
return (
|
||||
<SendouButton
|
||||
size="miniscule"
|
||||
shape="circle"
|
||||
icon={<UserPlus />}
|
||||
isDisabled={fetcher.state !== "idle" || fetcher.data === null}
|
||||
aria-label="Accept friend request"
|
||||
onPress={() =>
|
||||
fetcher.submit(
|
||||
{
|
||||
_action: "ACCEPT_REQUEST",
|
||||
friendRequestId: incomingFriendRequestId,
|
||||
},
|
||||
{ method: "post", action: FRIENDS_PAGE },
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const requestPending =
|
||||
hasPendingFriendRequest ||
|
||||
fetcher.state !== "idle" ||
|
||||
fetcher.data === null;
|
||||
sentFriendRequest || fetcher.state !== "idle" || fetcher.data === null;
|
||||
|
||||
if (requestPending) {
|
||||
return (
|
||||
|
||||
@@ -23,7 +23,8 @@ export const loader = async ({
|
||||
if (!viewer || Number.isNaN(targetUserId)) {
|
||||
return {
|
||||
isFriend: false,
|
||||
hasPendingFriendRequest: false,
|
||||
sentFriendRequest: false,
|
||||
incomingFriendRequestId: null,
|
||||
mutualFriends: [],
|
||||
};
|
||||
}
|
||||
@@ -50,7 +51,11 @@ export const loader = async ({
|
||||
|
||||
return {
|
||||
isFriend: Boolean(friendship),
|
||||
hasPendingFriendRequest: Boolean(pendingRequest),
|
||||
sentFriendRequest: pendingRequest?.senderId === viewer.id,
|
||||
incomingFriendRequestId:
|
||||
pendingRequest && pendingRequest.senderId !== viewer.id
|
||||
? pendingRequest.id
|
||||
: null,
|
||||
mutualFriends,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,8 +25,10 @@ export interface UserCardData extends CommonUser {
|
||||
*/
|
||||
export interface UserCardFriendship {
|
||||
isFriend: boolean;
|
||||
/** Whether a friend request between the viewer and this user is currently pending. */
|
||||
hasPendingFriendRequest: boolean;
|
||||
/** Whether the viewer has a pending friend request sent to this user. */
|
||||
sentFriendRequest: boolean;
|
||||
/** Id of this user's pending friend request to the viewer, or `null` when there is none. */
|
||||
incomingFriendRequestId: number | null;
|
||||
mutualFriends: Array<CommonUser>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { NZAP_TEST_ID } from "~/db/seed/constants";
|
||||
import { ADMIN_ID } from "~/features/admin/admin-constants";
|
||||
import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls";
|
||||
import { FRIENDS_PAGE, LFG_PAGE, SENDOUQ_LOOKING_PAGE } from "~/utils/urls";
|
||||
import {
|
||||
expect,
|
||||
impersonate,
|
||||
navigate,
|
||||
seed,
|
||||
selectUser,
|
||||
submit,
|
||||
test,
|
||||
waitForPOSTResponse,
|
||||
} from "./helpers/playwright";
|
||||
|
||||
test.describe("User card", () => {
|
||||
@@ -37,3 +40,54 @@ test.describe("User card", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("User card friend request", () => {
|
||||
test("receiver sees add friend button that accepts the incoming request", async ({
|
||||
page,
|
||||
}) => {
|
||||
await seed(page);
|
||||
await impersonate(page, NZAP_TEST_ID);
|
||||
await navigate({ page, url: FRIENDS_PAGE });
|
||||
|
||||
await selectUser({ page, userName: "Sendou", labelName: "User" });
|
||||
await submit(page);
|
||||
await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible();
|
||||
|
||||
await impersonate(page);
|
||||
await navigate({ page, url: LFG_PAGE });
|
||||
|
||||
await page.getByRole("button", { name: "N-ZAP" }).first().click();
|
||||
|
||||
const acceptButton = page.getByLabel("Accept friend request");
|
||||
await expect(acceptButton).toBeVisible();
|
||||
await expect(page.getByLabel("Friend request pending")).not.toBeVisible();
|
||||
|
||||
await waitForPOSTResponse(page, () => acceptButton.click());
|
||||
|
||||
await expect(page.getByText("Friend request accepted")).toBeAttached();
|
||||
await expect(acceptButton).not.toBeVisible();
|
||||
await expect(page.getByLabel("Send friend request")).not.toBeVisible();
|
||||
|
||||
await navigate({ page, url: FRIENDS_PAGE });
|
||||
await expect(page.getByRole("button", { name: "N-ZAP" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("sender still sees pending state on the receiver's card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await seed(page);
|
||||
await impersonate(page);
|
||||
await navigate({ page, url: FRIENDS_PAGE });
|
||||
|
||||
await selectUser({ page, userName: "N-ZAP", labelName: "User" });
|
||||
await submit(page);
|
||||
await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible();
|
||||
|
||||
await navigate({ page, url: LFG_PAGE });
|
||||
await page.getByRole("button", { name: "N-ZAP" }).first().click();
|
||||
|
||||
await expect(page.getByLabel("Friend request pending")).toBeVisible();
|
||||
await expect(page.getByLabel("Friend request pending")).toBeDisabled();
|
||||
await expect(page.getByLabel("Accept friend request")).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user