Can like groups and move them to another column

This commit is contained in:
Kalle
2022-01-29 22:50:24 +02:00
parent 056f106612
commit 963c30a66e
7 changed files with 238 additions and 43 deletions

View File

@@ -12,7 +12,7 @@ export function Avatar({
"--_avatar-size": tiny ? "2rem" : undefined,
};
return (
<div style={style} className="avatar__placeholder">
<div style={style} className={clsx("avatar__placeholder", { tiny })}>
{user.discordAvatar && (
<img
alt=""

View File

@@ -1,5 +1,11 @@
export function isGroupAdmin({group, user}: {group: {members: {captain: boolean, memberId: string}[]}, user: {id: string}}) {
return group.members.some(
(member) => member.captain && member.memberId === user.id
)
}
export function isGroupAdmin({
group,
user,
}: {
group: { members: { captain: boolean; memberId: string }[] };
user: { id: string };
}) {
return group.members.some(
(member) => member.captain && member.memberId === user.id
);
}

View File

@@ -27,6 +27,38 @@ export function create({
});
}
export function like({
likerId,
targetId,
}: {
likerId: string;
targetId: string;
}) {
return db.lfgGroupLike.create({
data: {
likerId,
targetId,
},
});
}
export function unlike({
likerId,
targetId,
}: {
likerId: string;
targetId: string;
}) {
return db.lfgGroupLike.delete({
where: {
likerId_targetId: {
likerId,
targetId,
},
},
});
}
export function findActiveByMember(user: { id: string }) {
return db.lfgGroup.findFirst({
where: {
@@ -39,6 +71,16 @@ export function findActiveByMember(user: { id: string }) {
},
include: {
members: true,
likedGroups: {
select: {
targetId: true,
},
},
likesReceived: {
select: {
likerId: true,
},
},
},
});
}

View File

@@ -1,31 +1,78 @@
import {
ActionFunction,
Form,
json,
LinksFunction,
LoaderFunction,
redirect,
useLoaderData,
} from "remix";
import { LFG_GROUP_FULL_SIZE } from "~/constants";
import * as LFGGroup from "~/models/LFGGroup.server";
import { requireUser, Unpacked } from "~/utils";
import styles from "~/styles/play-looking.css";
import { z } from "zod";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { LFG_GROUP_FULL_SIZE } from "~/constants";
import { 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";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
interface LookingLoaderData {
groups: {
const actionSchema = z.object({
_action: z.enum(["LIKE", "UNLIKE", "UNITE_GROUP", "MATCH_UP"]),
targetGroupId: z.string().uuid(),
});
export const action: ActionFunction = async ({ request, context }) => {
const data = await parseRequestFormData({
request,
schema: actionSchema,
});
const user = requireUser(context);
const group = await LFGGroup.findActiveByMember(user);
validate(group, "No active group");
validate(group.looking, "Group is not looking");
validate(isGroupAdmin({ group, user }), "Not group admin");
switch (data._action) {
case "UNITE_GROUP":
case "MATCH_UP":
case "UNLIKE": {
await LFGGroup.unlike({
likerId: group.id,
targetId: data.targetGroupId,
});
break;
}
case "LIKE": {
await LFGGroup.like({ likerId: group.id, targetId: data.targetGroupId });
break;
}
}
return { ok: data._action };
// TODO: notify watchers
};
type LookingLoaderDataGroup = {
id: string;
members?: {
id: string;
members?: {
id: string;
discordId: string;
discordAvatar: string | null;
discordName: string;
discordDiscriminator: string;
}[];
discordId: string;
discordAvatar: string | null;
discordName: string;
discordDiscriminator: string;
}[];
};
interface LookingLoaderData {
likedGroups: LookingLoaderDataGroup[];
neutralGroups: LookingLoaderDataGroup[];
likerGroups: LookingLoaderDataGroup[];
}
export const loader: LoaderFunction = async ({ context }) => {
@@ -46,10 +93,18 @@ export const loader: LoaderFunction = async ({ context }) => {
return LFG_GROUP_FULL_SIZE - ownGroup.members.length;
})();
const lookingForMatch = ownGroup.members.length === LFG_GROUP_FULL_SIZE;
return json<LookingLoaderData>({
groups: groups
const likesGiven = ownGroup.likedGroups.reduce(
(acc, lg) => acc.add(lg.targetId),
new Set<string>()
);
const likesReceived = ownGroup.likesReceived.reduce(
(acc, lg) => acc.add(lg.likerId),
new Set<string>()
);
return json<LookingLoaderData>(
groups
.filter((group) => group.members.length <= maxGroupSizeToConsider)
.filter((group) => group.id !== ownGroup.id)
.map((group) => ({
@@ -60,8 +115,24 @@ export const loader: LoaderFunction = async ({ context }) => {
group.ranked && lookingForMatch
? undefined
: group.members.map((member) => member.user),
})),
});
}))
.reduce(
(acc: LookingLoaderData, group) => {
// likesReceived first so that if both received like and
// given like then handle this edge case by just displaying the
// group as waiting like back
if (likesReceived.has(group.id)) {
acc.likerGroups.push(group);
} else if (likesGiven.has(group.id)) {
acc.likedGroups.push(group);
} else {
acc.neutralGroups.push(group);
}
return acc;
},
{ likedGroups: [], neutralGroups: [], likerGroups: [] }
)
);
};
export default function LookingPage() {
@@ -72,15 +143,48 @@ export default function LookingPage() {
<div className="play-looking__columns">
<div>
<h2 className="play-looking__column-header">You want to play with</h2>
<div className="play-looking__cards">
{data.likedGroups.map((group) => {
return (
<GroupCard
key={group.id}
group={group}
isGroupAdmin
type="LIKES_GIVEN"
/>
);
})}
</div>
</div>
<div>
<h2 className="play-looking__column-header invisible">Groups</h2>
{data.groups.map((group) => {
return <GroupCard key={group.id} group={group} />;
})}
<div className="play-looking__cards">
{data.neutralGroups.map((group) => {
return (
<GroupCard
key={group.id}
group={group}
isGroupAdmin
type="NEUTRAL"
/>
);
})}
</div>
</div>
<div>
<h2 className="play-looking__column-header">Want to play with you</h2>
<div className="play-looking__cards">
{data.likerGroups.map((group) => {
return (
<GroupCard
key={group.id}
group={group}
isGroupAdmin
type="LIKES_RECEIVED"
/>
);
})}
</div>
</div>
</div>
</div>
@@ -89,21 +193,42 @@ export default function LookingPage() {
function GroupCard({
group,
isGroupAdmin = false,
type,
}: {
group: Unpacked<LookingLoaderData["groups"]>;
group: LookingLoaderDataGroup;
isGroupAdmin?: boolean;
type: "LIKES_GIVEN" | "NEUTRAL" | "LIKES_RECEIVED";
}) {
const buttonText = () => {
if (type === "LIKES_GIVEN") return "idk";
if (type === "NEUTRAL") return "Let's play!";
return "Group up";
};
return (
<div className="play-looking__card">
{group.members?.map((member) => {
return (
<div key={member.id} className="play-looking__member-card">
<Avatar tiny user={member} />
<span className="play-looking__member-name">
{member.discordName}
</span>
</div>
);
})}
</div>
<Form method="post">
<div className="play-looking__card">
<div className="play-looking__card__members">
{group.members?.map((member) => {
return (
<div key={member.id} className="play-looking__member-card">
<Avatar tiny user={member} />
<span className="play-looking__member-name">
{member.discordName}
</span>
</div>
);
})}
</div>
<input type="hidden" name="targetGroupId" value={group.id} />
{isGroupAdmin && (
<Button type="submit" name="_action" value="LIKE" tiny>
{buttonText()}
</Button>
)}
</div>
</Form>
);
}

View File

@@ -446,6 +446,10 @@ select::selection {
inline-size: var(--_avatar-size, 2.5rem);
}
.avatar__placeholder.tiny {
border-radius: 14px;
}
/* Utility classes */
.mt-2 {

View File

@@ -1,8 +1,15 @@
.play-looking__columns {
display: grid;
column-gap: var(--s-5);
grid-template-columns: repeat(3, 1fr);
}
.play-looking__cards {
display: flex;
flex-direction: column;
gap: var(--s-3);
}
.play-looking__column-header {
font-size: var(--fonts-md);
text-align: center;
@@ -10,16 +17,26 @@
.play-looking__card {
display: flex;
width: 100%;
justify-content: space-evenly;
padding: var(--s-4);
flex-direction: column;
align-items: center;
background-color: var(--bg-lighter);
border-radius: var(--rounded);
gap: var(--s-4);
padding-block: var(--s-3);
padding-inline: var(--s-2);
}
.play-looking__card__members {
display: flex;
width: 100%;
flex-wrap: wrap;
justify-content: center;
gap: var(--s-2);
}
.play-looking__member-card {
display: flex;
min-width: calc(50% - var(--s-4));
max-width: calc(50% - var(--s-4));
flex: 1;
flex-direction: column;

View File

@@ -412,6 +412,7 @@ export async function seed(variation?: SeedVariations) {
data: [
{
memberId: ADMIN_TEST_UUID,
captain: true,
},
{ memberId: NZAP_TEST_UUID },
],