LFG Match page initial

This commit is contained in:
Kalle
2022-02-05 10:42:06 +02:00
parent c1e46b84d6
commit 2a9f12981b
5 changed files with 198 additions and 17 deletions

View File

@@ -0,0 +1,8 @@
import { db } from "~/utils/db.server";
export function findById(id: string) {
return db.lfgGroupMatch.findUnique({
where: { id },
select: { groups: { include: { members: { include: { user: true } } } } },
});
}

View File

@@ -17,7 +17,7 @@ import { uniteGroupInfo } from "~/core/play/utils";
import { canUniteWithGroup, isGroupAdmin } from "~/core/play/validators";
import * as LFGGroup from "~/models/LFGGroup.server";
import styles from "~/styles/play-looking.css";
import { parseRequestFormData, requireUser, validate } from "~/utils";
import { parseRequestFormData, requireUser, UserLean, validate } from "~/utils";
import {
skillToMMR,
teamSkillToApproximateMMR,
@@ -143,14 +143,9 @@ export const action: ActionFunction = async ({ request, context }) => {
export type LookingLoaderDataGroup = {
id: string;
members?: {
id: string;
discordId: string;
discordAvatar: string | null;
discordName: string;
discordDiscriminator: string;
members?: (UserLean & {
MMR?: number;
}[];
})[];
teamMMR?: {
exact: boolean;
value: number;
@@ -164,7 +159,7 @@ interface LookingLoaderData {
likerGroups: LookingLoaderDataGroup[];
ownGroup: LookingLoaderDataGroup;
type: LfgGroupType;
userIsCaptain: boolean;
isCaptain: boolean;
}
export const loader: LoaderFunction = async ({ context }) => {
@@ -223,7 +218,7 @@ export const loader: LoaderFunction = async ({ context }) => {
return json<LookingLoaderData>({
ownGroup: ownGroupForResponse,
type: ownGroup.type,
userIsCaptain: isGroupAdmin({ group: ownGroup, user }),
isCaptain: isGroupAdmin({ group: ownGroup, user }),
...groups
.filter(
(group) =>
@@ -257,7 +252,7 @@ export const loader: LoaderFunction = async ({ context }) => {
}))
.reduce(
(
acc: Omit<LookingLoaderData, "ownGroup" | "type" | "userIsCaptain">,
acc: Omit<LookingLoaderData, "ownGroup" | "type" | "isCaptain">,
group
) => {
// likesReceived first so that if both received like and
@@ -292,7 +287,7 @@ export default function LookingPage() {
</div>
<div className="play-looking__waves-button">
<Form method="post">
{data.userIsCaptain && (
{data.isCaptain && (
<Button
type="submit"
name="_action"
@@ -327,7 +322,7 @@ export default function LookingPage() {
<GroupCard
key={group.id}
group={group}
isCaptain={data.userIsCaptain}
isCaptain={data.isCaptain}
type="LIKES_GIVEN"
ranked={group.ranked}
lookingForMatch={lookingForMatch}
@@ -344,7 +339,7 @@ export default function LookingPage() {
<GroupCard
key={group.id}
group={group}
isCaptain={data.userIsCaptain}
isCaptain={data.isCaptain}
type="NEUTRAL"
ranked={group.ranked}
lookingForMatch={lookingForMatch}
@@ -361,7 +356,7 @@ export default function LookingPage() {
<GroupCard
key={group.id}
group={group}
isCaptain={data.userIsCaptain}
isCaptain={data.isCaptain}
type="LIKES_RECEIVED"
ranked={data.ownGroup.ranked}
lookingForMatch={lookingForMatch}

View File

@@ -1,3 +1,119 @@
export default function MatchPage() {
return <div>match :D</div>;
import {
Form,
json,
LinksFunction,
LoaderFunction,
useLoaderData,
} from "remix";
import invariant from "tiny-invariant";
import { Button } from "~/components/Button";
import { DISCORD_URL } from "~/constants";
import * as LFGMatch from "~/models/LFGMatch.server";
import { getUser, UserLean } from "~/utils";
import styles from "~/styles/play-match.css";
import { Avatar } from "~/components/Avatar";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
interface LFGMatchLoaderData {
/** Can the user counterpick and report scores? */
isCaptain: boolean;
isRanked: boolean;
groups: UserLean[][];
}
export const loader: LoaderFunction = async ({ params, context }) => {
invariant(typeof params.id === "string", "Expected params.bid to be string");
const user = getUser(context);
const match = await LFGMatch.findById(params.id);
if (!match) return new Response(null, { status: 404 });
//const isRanked = match.groups.every((g) => g.ranked);
const isRanked = false;
const isOwnMatch = match.groups.some((g) =>
g.members.some((m) => user?.id === m.user.id)
);
// Non-ranked matches are only of interest to participants
if (!isRanked && !isOwnMatch) {
return new Response(null, { status: 404 });
}
const isCaptain = match.groups.some((g) =>
g.members.some((m) => m.user.id === user?.id && m.captain)
);
return json<LFGMatchLoaderData>({
isCaptain,
isRanked,
groups: match.groups
.sort((a, b) => {
const aIsOwnGroup = a.members.some((m) => user?.id === m.user.id);
const bIsOwnGroup = b.members.some((m) => user?.id === m.user.id);
return Number(bIsOwnGroup) - Number(aIsOwnGroup);
})
.map((g) => {
return g.members.map((g) => ({
id: g.user.id,
discordId: g.user.discordId,
discordAvatar: g.user.discordAvatar,
discordName: g.user.discordName,
discordDiscriminator: g.user.discordDiscriminator,
}));
}),
});
};
export default function LFGMatchPage() {
const data = useLoaderData<LFGMatchLoaderData>();
return (
<div className="container">
<div className="play-match__waves">
<div className="play-match__teams">
{data.groups.map((g, i) => {
return (
<div
key={i}
className="play-match__waves-section play-match__players"
>
{g.map((user) => (
<div key={user.id} className="play-match__player">
<Avatar user={user} />
<span className="play-match__player-name">
{user.discordName}
</span>
</div>
))}
</div>
);
})}
</div>
{!data.isRanked && (
<div className="play-match__waves-section play-match__info">
This is your match! You can reach out to your opponents{" "}
<a href={DISCORD_URL}>our Discord</a> in the{" "}
<code>#match-meetup</code> channel.
</div>
)}
</div>
<div className="play-match__waves-button">
<Form method="post">
{data.isCaptain && (
<Button
type="submit"
name="_action"
value="LOOK_AGAIN"
tiny
variant="outlined"
>
Look again
</Button>
)}
</Form>
</div>
</div>
);
}

53
app/styles/play-match.css Normal file
View File

@@ -0,0 +1,53 @@
.play-match__waves {
padding: var(--s-8);
background: url("/svg/background-pattern.svg");
background-color: var(--bg-lighter);
border-radius: var(--rounded);
}
.play-match__teams {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--s-4);
}
.play-match__waves-section {
padding: var(--s-4);
background-color: var(--bg-darker);
border-radius: var(--rounded);
font-size: var(--fonts-sm);
text-align: center;
}
.play-match__info {
margin-block-start: var(--s-8);
}
.play-match__players {
display: grid;
max-width: 14rem;
flex: 1;
gap: var(--s-4);
grid-template-columns: 1fr 1fr;
}
.play-match__player-name {
font-size: var(--fonts-xs);
font-weight: var(--semi-bold);
margin-block-start: 1px;
white-space: nowrap;
}
.play-match__player {
display: flex;
flex-direction: column;
align-items: center;
font-size: var(--fonts-xs);
}
.play-match__waves-button {
display: flex;
justify-content: center;
margin-block-start: var(--s-4);
}

View File

@@ -150,3 +150,12 @@ export interface MyCSSProperties extends CSSProperties {
"--brackets-column-matches"?: number;
"--tabs-count"?: number;
}
/** Minimal information on user to show their name and avatar */
export interface UserLean {
id: string;
discordId: string;
discordAvatar: string | null;
discordName: string;
discordDiscriminator: string;
}