Allow user to leave group Closes #749

This commit is contained in:
Kalle 2022-02-24 09:37:41 +02:00
parent 436b44d2d8
commit fc835eae55
3 changed files with 90 additions and 36 deletions

View File

@ -9,39 +9,47 @@ import { GroupMembers } from "./GroupMembers";
export function GroupCard({
group,
canTakeAction = false,
type,
action,
showAction,
ranked,
lookingForMatch,
isOwnGroup = false,
}: {
group: LookingLoaderDataGroup;
canTakeAction?: boolean;
type?: "LIKES_GIVEN" | "NEUTRAL" | "LIKES_RECEIVED";
action: Exclude<LookingActionSchema["_action"], "UNEXPIRE">;
showAction: boolean;
ranked?: boolean;
lookingForMatch: boolean;
isOwnGroup?: boolean;
}) {
const fetcher = useFetcher();
const buttonText = () => {
if (isOwnGroup) return "Stop looking";
if (type === "LIKES_GIVEN") return "Undo";
if (type === "NEUTRAL") return "Let's play?";
return lookingForMatch ? "Match up" : "Group up";
};
const buttonValue = (): LookingActionSchema["_action"] => {
if (isOwnGroup) return "LOOK_AGAIN";
if (type === "LIKES_GIVEN") return "UNLIKE";
if (type === "NEUTRAL") return "LIKE";
return lookingForMatch ? "MATCH_UP" : "UNITE_GROUPS";
switch (action) {
case "LEAVE_GROUP":
return "Leave group";
case "LIKE":
return "Let's play?";
case "UNLIKE":
return "Undo";
case "UNITE_GROUPS":
return "Group up";
case "MATCH_UP":
return "Match up";
case "LOOK_AGAIN":
return "Stop looking";
default:
throw new Error(`Invalid group action type: ${action}`);
}
};
const buttonVariant = (): ButtonProps["variant"] => {
if (isOwnGroup) return "minimal-destructive";
return type === "LIKES_GIVEN" ? "destructive" : undefined;
switch (action) {
case "LEAVE_GROUP":
case "LOOK_AGAIN":
return "minimal-destructive";
case "UNLIKE":
return "destructive";
default:
return undefined;
}
};
return (
@ -60,14 +68,14 @@ export function GroupCard({
</div>
)}
<input type="hidden" name="targetGroupId" value={group.id} />
{type === "LIKES_RECEIVED" && (
{action === "UNITE_GROUPS" && (
<input
type="hidden"
name="targetGroupSize"
value={group.members?.length ?? -1}
/>
)}
{canTakeAction && (
{showAction && (
<Button
className={
isOwnGroup
@ -76,7 +84,7 @@ export function GroupCard({
}
type="submit"
name="_action"
value={buttonValue()}
value={action}
tiny
variant={buttonVariant()}
loading={fetcher.state !== "idle"}

View File

@ -234,6 +234,28 @@ export function setInactive(id: string) {
});
}
export function leaveGroup({
groupId,
memberId,
}: {
groupId: string;
memberId: string;
}) {
// delete many so we don't throw in case
// the group was just integrated into another
// group
return db.lfgGroupMember.deleteMany({
where: {
groupId,
memberId,
// no escaping group if match has been formed
group: {
matchId: null,
},
},
});
}
export function unexpire(groupId: string) {
return db.lfgGroup.update({
where: {

View File

@ -69,6 +69,9 @@ const lookingActionSchema = z.union([
z.object({
_action: z.literal("LOOK_AGAIN"),
}),
z.object({
_action: z.literal("LEAVE_GROUP"),
}),
z.object({
_action: z.literal("UNEXPIRE"),
}),
@ -84,10 +87,12 @@ export const action: ActionFunction = async ({ request, context }) => {
const ownGroup = await LFGGroup.findActiveByMember(user);
validate(ownGroup, "No active group");
validate(ownGroup.looking, "Group is not looking");
validate(isGroupAdmin({ group: ownGroup, user }), "Not group admin");
const validateIsGroupAdmin = () =>
validate(isGroupAdmin({ group: ownGroup, user }), "Not group admin");
switch (data._action) {
case "UNITE_GROUPS": {
validateIsGroupAdmin();
validate(
canUniteWithGroup({
ownGroupType: ownGroup.type,
@ -132,6 +137,7 @@ export const action: ActionFunction = async ({ request, context }) => {
break;
}
case "MATCH_UP": {
validateIsGroupAdmin();
const groupToMatchUpWith = await LFGGroup.findById(data.targetGroupId);
validate(groupToMatchUpWith, "Invalid targetGroupId");
@ -142,6 +148,7 @@ export const action: ActionFunction = async ({ request, context }) => {
break;
}
case "UNLIKE": {
validateIsGroupAdmin();
await LFGGroup.unlike({
likerId: ownGroup.id,
targetId: data.targetGroupId,
@ -149,6 +156,7 @@ export const action: ActionFunction = async ({ request, context }) => {
break;
}
case "LIKE": {
validateIsGroupAdmin();
await LFGGroup.like({
likerId: ownGroup.id,
targetId: data.targetGroupId,
@ -156,10 +164,16 @@ export const action: ActionFunction = async ({ request, context }) => {
break;
}
case "LOOK_AGAIN": {
validateIsGroupAdmin();
await LFGGroup.setInactive(ownGroup.id);
return redirect("/play");
}
case "LEAVE_GROUP": {
await LFGGroup.leaveGroup({ memberId: user.id, groupId: ownGroup.id });
return redirect("/play");
}
case "UNEXPIRE": {
validateIsGroupAdmin();
await LFGGroup.unexpire(ownGroup.id);
break;
}
@ -288,12 +302,23 @@ export default function LookingPage() {
const lookingForMatch = data.ownGroup.members?.length === LFG_GROUP_FULL_SIZE;
const columns = [
{ type: "LIKES_GIVEN", groups: data.likedGroups, title: "Liked" },
{ type: "NEUTRAL", groups: data.neutralGroups, title: "Neutral" },
{
type: "LIKES_GIVEN",
groups: data.likedGroups,
title: "Liked",
action: "UNLIKE",
},
{
type: "NEUTRAL",
groups: data.neutralGroups,
title: "Neutral",
action: "LIKE",
},
{
type: "LIKES_RECEIVED",
groups: data.likerGroups,
title: lookingForMatch ? "Match up" : "Group up",
action: lookingForMatch ? "MATCH_UP" : "UNITE_GROUPS",
},
] as const;
@ -312,10 +337,11 @@ export default function LookingPage() {
<GroupCard
group={data.ownGroup}
ranked={data.ownGroup.ranked}
lookingForMatch={false}
isOwnGroup
// we can stop looking even if the group expired
canTakeAction={data.isCaptain}
action={data.isCaptain ? "LOOK_AGAIN" : "LEAVE_GROUP"}
// we can stop looking or leave team
// even if team is expired
showAction
/>
<LookingInfoText lastUpdated={lastUpdated} />
<hr className="play-looking__divider" />
@ -335,14 +361,13 @@ export default function LookingPage() {
<GroupCard
key={group.id}
group={group}
canTakeAction={canTakeAction}
type={column.type}
action={column.action}
showAction={canTakeAction}
ranked={
column.type === "LIKES_RECEIVED" && !lookingForMatch
? data.ownGroup.ranked
: group.ranked
}
lookingForMatch={lookingForMatch}
/>
);
})}
@ -360,14 +385,13 @@ export default function LookingPage() {
<GroupCard
key={group.id}
group={group}
canTakeAction={canTakeAction}
type={column.type}
action={column.action}
showAction={canTakeAction}
ranked={
column.type === "LIKES_RECEIVED" && !lookingForMatch
? data.ownGroup.ranked
: group.ranked
}
lookingForMatch={lookingForMatch}
/>
);
})}