Skip to content

Commit

Permalink
Replay indicator + add trust
Browse files Browse the repository at this point in the history
  • Loading branch information
Sendouc committed Aug 16, 2023
1 parent d473cdd commit 33256cb
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 34 deletions.
28 changes: 10 additions & 18 deletions app/components/SubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,16 @@ export function SubmitButton({
return undefined;
};

// render action as hidden input to deal with a bug in older Safari versions
const isActionValue = name() === "_action";

return (
<>
{isActionValue ? (
<input type="hidden" name="_action" value={_action} />
) : null}
<Button
{...rest}
disabled={rest.disabled || isSubmitting}
type="submit"
name={!isActionValue ? name() : undefined}
value={!isActionValue ? value() : undefined}
data-testid={testId ?? "submit-button"}
>
{children}
</Button>
</>
<Button
{...rest}
disabled={rest.disabled || isSubmitting}
type="submit"
name={name()}
value={value()}
data-testid={testId ?? "submit-button"}
>
{children}
</Button>
);
}
11 changes: 9 additions & 2 deletions app/features/sendouq/components/GroupCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,15 @@ export function GroupCard({
{group.tier ? (
<div className="stack xs text-lighter font-bold items-center justify-center text-xs">
<TierImage tier={group.tier} width={100} />
{group.tier.name}
{group.tier.isPlus ? "+" : ""}
<div>
{group.tier.name}
{group.tier.isPlus ? "+" : ""}{" "}
{group.isReplay ? (
<>
/ <span className="text-theme-secondary">REPLAY</span>
</>
) : null}
</div>
</div>
) : null}
{action && (ownRole === "OWNER" || ownRole === "MANAGER") ? (
Expand Down
43 changes: 43 additions & 0 deletions app/features/sendouq/core/groups.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
SkillTierInterval,
TieredSkill,
} from "~/features/mmr/tiered.server";
import type { RecentMatchPlayer } from "../queries/findRecentMatchPlayersByUserId.server";

export function divideGroups({
groups,
Expand Down Expand Up @@ -97,6 +98,48 @@ export function filterOutGroupsWithIncompatibleMapListPreference(
};
}

const MIN_PLAYERS_FOR_REPLAY = 3;
export function addReplayIndicator({
groups,
recentMatchPlayers,
userId,
}: {
groups: DividedGroupsUncensored;
recentMatchPlayers: RecentMatchPlayer[];
userId: number;
}): DividedGroupsUncensored {
if (!recentMatchPlayers.length) return groups;

const ownGroupId = recentMatchPlayers.find(
(u) => u.userId === userId
)?.groupId;
invariant(ownGroupId, "own group not found");
const otherGroupId = recentMatchPlayers.find(
(u) => u.groupId !== ownGroupId
)?.groupId;
invariant(otherGroupId, "other group not found");

const opponentPlayers = recentMatchPlayers
.filter((u) => u.groupId === otherGroupId)
.map((p) => p.userId);

const addReplayIndicatorIfNeeded = (group: LookingGroupWithInviteCode) => {
const samePlayersCount = group.members.reduce(
(acc, cur) => (opponentPlayers.includes(cur.id) ? acc + 1 : acc),
0
);

return { ...group, isReplay: samePlayersCount >= MIN_PLAYERS_FOR_REPLAY };
};

return {
own: groups.own,
likesGiven: groups.likesGiven.map(addReplayIndicatorIfNeeded),
likesReceived: groups.likesReceived.map(addReplayIndicatorIfNeeded),
neutral: groups.neutral.map(addReplayIndicatorIfNeeded),
};
}

const censorGroupFully = ({
inviteCode: _inviteCode,
...group
Expand Down
3 changes: 3 additions & 0 deletions app/features/sendouq/q-schemas.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const frontPageSchema = z.union([
z.object({
_action: z.literal("JOIN_TEAM"),
}),
z.object({
_action: z.literal("JOIN_TEAM_WITH_TRUST"),
}),
z.object({
_action: z.literal("SET_INITIAL_SP"),
tier: z.enum(["higher", "default", "lower"]),
Expand Down
1 change: 1 addition & 0 deletions app/features/sendouq/q-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type LookingGroup = {
id: number;
mapListPreference?: Group["mapListPreference"];
tier?: TieredSkill["tier"];
isReplay?: boolean;
members?: {
id: number;
discordId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { sql } from "~/db/sql";
import type { GroupMember } from "~/db/types";

const stm = sql.prepare(/* sql*/ `
with "MostRecentGroupMatch" as (
select
"GroupMatch".*
from "GroupMember"
left join "Group" on "Group"."id" = "GroupMember"."groupId"
inner join "GroupMatch" on "GroupMatch"."alphaGroupId" = "Group"."id"
or "GroupMatch"."bravoGroupId" = "Group"."id"
where
"GroupMember"."userId" = @userId
order by "GroupMatch"."createdAt" desc
limit 1
)
select
"GroupMember"."groupId",
"GroupMember"."userId"
from "MostRecentGroupMatch"
left join "Group" on "Group"."id" = "MostRecentGroupMatch"."alphaGroupId"
or "Group"."id" = "MostRecentGroupMatch"."bravoGroupId"
left join "GroupMember" on "GroupMember"."groupId" = "Group"."id"
where
"MostRecentGroupMatch"."createdAt" > unixepoch() - 60 * 60 * 2
`);

export type RecentMatchPlayer = Pick<GroupMember, "groupId" | "userId">;

export function findRecentMatchPlayersByUserId(userId: number) {
return stm.all({ userId }) as Array<RecentMatchPlayer>;
}
20 changes: 13 additions & 7 deletions app/features/sendouq/queries/findTeamByInviteCode.server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { sql } from "~/db/sql";
import type { Group } from "~/db/types";
import { parseDBArray } from "~/utils/sql";
import type { Group, GroupMember } from "~/db/types";
import { parseDBJsonArray } from "~/utils/sql";

const stm = sql.prepare(/* sql */ `
select
"Group"."id",
"Group"."status",
json_group_array(
"User"."discordName"
json_object(
'id', "User"."id",
'discordName', "User"."discordName",
'role', "GroupMember"."role"
)
) as "members"
from
"Group"
Expand All @@ -19,15 +23,17 @@ const stm = sql.prepare(/* sql */ `
group by "Group"."id"
`);

export function findTeamByInviteCode(
inviteCode: string
): { id: number; status: Group["status"]; members: string[] } | null {
export function findTeamByInviteCode(inviteCode: string): {
id: number;
status: Group["status"];
members: { id: number; discordName: string; role: GroupMember["role"] }[];
} | null {
const row = stm.get({ inviteCode }) as any;
if (!row) return null;

return {
id: row.id,
status: row.status,
members: parseDBArray(row.members),
members: parseDBJsonArray(row.members),
};
}
16 changes: 13 additions & 3 deletions app/features/sendouq/routes/q.looking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Main } from "~/components/Main";
import { SubmitButton } from "~/components/SubmitButton";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useTranslation } from "~/hooks/useTranslation";
import { getUser, requireUserId } from "~/modules/auth/user.server";
import { getUserId, requireUserId } from "~/modules/auth/user.server";
import { MapPool } from "~/modules/map-pool-serializer";
import {
parseRequestFormData,
Expand All @@ -31,6 +31,7 @@ import {
import { GroupCard } from "../components/GroupCard";
import { groupAfterMorph, hasGroupManagerPerms } from "../core/groups";
import {
addReplayIndicator,
addSkillsToGroups,
censorGroups,
divideGroups,
Expand Down Expand Up @@ -68,6 +69,7 @@ import { useWindowSize } from "~/hooks/useWindowSize";
import { Tab, Tabs } from "~/components/Tabs";
import { useAutoRefresh } from "~/hooks/useAutoRefresh";
import { groupHasMatch } from "../queries/groupHasMatch.server";
import { findRecentMatchPlayersByUserId } from "../queries/findRecentMatchPlayersByUserId.server";

export const handle: SendouRouteHandle = {
i18n: ["q"],
Expand Down Expand Up @@ -285,7 +287,7 @@ export const action: ActionFunction = async ({ request }) => {
};

export const loader = async ({ request }: LoaderArgs) => {
const user = await getUser(request);
const user = await getUserId(request);

const currentGroup = user ? findCurrentGroupByUserId(user.id) : undefined;
const redirectLocation = groupRedirectLocationByCurrentLocation({
Expand Down Expand Up @@ -323,8 +325,16 @@ export const loader = async ({ request }: LoaderArgs) => {
? filterOutGroupsWithIncompatibleMapListPreference(groupsWithSkills)
: groupsWithSkills;

const groupsWithReplayIndicator = groupIsFull
? addReplayIndicator({
groups: compatibleGroups,
recentMatchPlayers: findRecentMatchPlayersByUserId(user!.id),
userId: user!.id,
})
: compatibleGroups;

const censoredGroups = censorGroups({
groups: compatibleGroups,
groups: groupsWithReplayIndicator,
showMembers: !groupIsFull,
showInviteCode: hasGroupManagerPerms(currentGroup.role) && !groupIsFull,
});
Expand Down
37 changes: 34 additions & 3 deletions app/features/sendouq/routes/q.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ import {
DEFAULT_SKILL_LOW,
DEFAULT_SKILL_MID,
} from "~/features/mmr/mmr-constants";
import { giveTrust } from "~/features/tournament/queries/giveTrust.server";
import type { GroupMember } from "~/db/types";
import invariant from "tiny-invariant";

export const handle: SendouRouteHandle = {
i18n: ["q"],
Expand Down Expand Up @@ -120,6 +123,7 @@ export const action: ActionFunction = async ({ request }) => {
data.direct === "true" ? SENDOUQ_LOOKING_PAGE : SENDOUQ_PREPARING_PAGE
);
}
case "JOIN_TEAM_WITH_TRUST":
case "JOIN_TEAM": {
const code = new URL(request.url).searchParams.get(
JOIN_CODE_SEARCH_PARAM_KEY
Expand All @@ -134,6 +138,15 @@ export const action: ActionFunction = async ({ request }) => {
groupId: teamInvitedTo.id,
userId: user.id,
});
if (data._action === "JOIN_TEAM_WITH_TRUST") {
const owner = teamInvitedTo.members.find((m) => m.role === "OWNER");
invariant(owner, "Owner not found");

giveTrust({
trustGiverUserId: user.id,
trustReceiverUserId: owner.id,
});
}

return redirect(
teamInvitedTo.status === "PREPARING"
Expand Down Expand Up @@ -372,28 +385,46 @@ function JoinTeamDialog({
}: {
open: boolean;
close: () => void;
members: string[];
members: {
discordName: string;
role: GroupMember["role"];
}[];
}) {
const fetcher = useFetcher();

const owner = members.find((m) => m.role === "OWNER");
invariant(owner, "Owner not found");

return (
<Dialog
isOpen={open}
close={close}
closeOnAnyClick={false}
className="text-center"
>
Join group with {joinListToNaturalString(members)}?
Join the group with{" "}
{joinListToNaturalString(members.map((m) => m.discordName))}?
<fetcher.Form
className="stack horizontal justify-center sm mt-4"
className="stack horizontal justify-center sm mt-4 flex-wrap"
method="post"
>
<SubmitButton _action="JOIN_TEAM" state={fetcher.state}>
Join
</SubmitButton>
<SubmitButton
_action="JOIN_TEAM_WITH_TRUST"
state={fetcher.state}
variant="outlined"
>
Join & trust {owner.discordName}
</SubmitButton>
<Button onClick={close} variant="destructive">
No thanks
</Button>
<FormMessage type="info">
Trusting a user allows them to add you to groups without an invite
link in the future
</FormMessage>
</fetcher.Form>
</Dialog>
);
Expand Down
2 changes: 1 addition & 1 deletion app/features/tournament/queries/giveTrust.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const stm = sql.prepare(/*sql */ `
) values (
@trustGiverUserId,
@trustReceiverUserId
)
) on conflict do nothing
`);

export function giveTrust({
Expand Down

0 comments on commit 33256cb

Please sign in to comment.