From 963c30a66e8f07ebdae181aff9907e294f551b70 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 29 Jan 2022 22:50:24 +0200 Subject: [PATCH] Can like groups and move them to another column --- app/components/Avatar.tsx | 2 +- app/core/play/validators.ts | 16 ++- app/models/LFGGroup.server.ts | 42 ++++++++ app/routes/play/looking.tsx | 193 ++++++++++++++++++++++++++++------ app/styles/global.css | 4 + app/styles/play-looking.css | 23 +++- prisma/seed/script.ts | 1 + 7 files changed, 238 insertions(+), 43 deletions(-) diff --git a/app/components/Avatar.tsx b/app/components/Avatar.tsx index be780c179..7f2003433 100644 --- a/app/components/Avatar.tsx +++ b/app/components/Avatar.tsx @@ -12,7 +12,7 @@ export function Avatar({ "--_avatar-size": tiny ? "2rem" : undefined, }; return ( -
+
{user.discordAvatar && ( member.captain && member.memberId === user.id - ) -} \ No newline at end of file +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 + ); +} diff --git a/app/models/LFGGroup.server.ts b/app/models/LFGGroup.server.ts index 56968a209..de5b0960f 100644 --- a/app/models/LFGGroup.server.ts +++ b/app/models/LFGGroup.server.ts @@ -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, + }, + }, }, }); } diff --git a/app/routes/play/looking.tsx b/app/routes/play/looking.tsx index 9ce7dc799..a991140b2 100644 --- a/app/routes/play/looking.tsx +++ b/app/routes/play/looking.tsx @@ -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({ - groups: groups + const likesGiven = ownGroup.likedGroups.reduce( + (acc, lg) => acc.add(lg.targetId), + new Set() + ); + const likesReceived = ownGroup.likesReceived.reduce( + (acc, lg) => acc.add(lg.likerId), + new Set() + ); + + return json( + 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() {

You want to play with

+
+ {data.likedGroups.map((group) => { + return ( + + ); + })} +

Groups

- {data.groups.map((group) => { - return ; - })} +
+ {data.neutralGroups.map((group) => { + return ( + + ); + })} +

Want to play with you

+
+ {data.likerGroups.map((group) => { + return ( + + ); + })} +
@@ -89,21 +193,42 @@ export default function LookingPage() { function GroupCard({ group, + isGroupAdmin = false, + type, }: { - group: Unpacked; + 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 ( -
- {group.members?.map((member) => { - return ( -
- - - {member.discordName} - -
- ); - })} -
+
+
+
+ {group.members?.map((member) => { + return ( +
+ + + {member.discordName} + +
+ ); + })} +
+ + {isGroupAdmin && ( + + )} +
+
); } diff --git a/app/styles/global.css b/app/styles/global.css index 46ac22f0e..2c3f25467 100644 --- a/app/styles/global.css +++ b/app/styles/global.css @@ -446,6 +446,10 @@ select::selection { inline-size: var(--_avatar-size, 2.5rem); } +.avatar__placeholder.tiny { + border-radius: 14px; +} + /* Utility classes */ .mt-2 { diff --git a/app/styles/play-looking.css b/app/styles/play-looking.css index 75c09968a..e62b02d17 100644 --- a/app/styles/play-looking.css +++ b/app/styles/play-looking.css @@ -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; diff --git a/prisma/seed/script.ts b/prisma/seed/script.ts index dcd23441d..ba104c868 100644 --- a/prisma/seed/script.ts +++ b/prisma/seed/script.ts @@ -412,6 +412,7 @@ export async function seed(variation?: SeedVariations) { data: [ { memberId: ADMIN_TEST_UUID, + captain: true, }, { memberId: NZAP_TEST_UUID }, ],