diff --git a/assets/js/react/components/FilterModal.tsx b/assets/js/react/components/FilterModal.tsx index a1aa10f..13f0b5e 100644 --- a/assets/js/react/components/FilterModal.tsx +++ b/assets/js/react/components/FilterModal.tsx @@ -10,6 +10,7 @@ import { Modal } from "./Modal" import { Mode, getModeName } from "../types/Mode" import { SelectLanguage, SelectLanguageProps } from "./SelectLanguage" import { SelectTitle, SelectTitleProps } from "./SelectTitle" +import { Site, getSiteName } from "../types/Site" import { Slider } from "./Slider" import { Title } from "../types/Title" import { @@ -77,6 +78,10 @@ export function FilterModal({ // Registration + const registerSites = register("sites", { + required: "Please select at least one site.", + }) + const proxyLanguages = register("languages") const registerLanguages: Pick< SelectLanguageProps, @@ -126,6 +131,21 @@ export function FilterModal({ }} >
+
+ +

+ Prioritize coaches from the selected site(s). +

+
+ {(Object.values(Site) as Site[]).map((s) => ( +
+ +
{getSiteName(s)}
+
+ ))} +
+
+ +
+ +

+ Prefer a specific game mode? We{"'"}ll prioritize coaches that + specialize in the modes selected. +

+
+ {(Object.values(Mode) as Mode[]).map((m) => ( +
+ +
{getModeName(m)}
+
+ ))} +
+
+

@@ -208,22 +244,6 @@ export function FilterModal({

- -
- -

- Prefer a specific game mode? We{"'"}ll prioritize coaches that - specialize in the modes selected. -

-
- {(Object.keys(Mode) as Mode[]).map((m) => ( -
- -
{getModeName(m)}
-
- ))} -
-
) diff --git a/assets/js/react/components/FilterScroll.tsx b/assets/js/react/components/FilterScroll.tsx index 0fe398a..7efe89e 100644 --- a/assets/js/react/components/FilterScroll.tsx +++ b/assets/js/react/components/FilterScroll.tsx @@ -6,13 +6,16 @@ import type { SearchParams } from "../types/SearchParams" import BulletIcon from "../icons/Bullet" import EnglishIcon from "../icons/English" import FilterIcon from "../icons/Filter" +import KnightIcon from "../icons/Knight" import LightningIcon from "../icons/Lightning" +import PawnIcon from "../icons/Pawn" import RabbitIcon from "../icons/Rabbit" import RightArrowIcon from "../icons/RightArrow" import RisingGraphIcon from "../icons/RisingGraph" import TrophyIcon from "../icons/Trophy" import { Button } from "./Button" import { Mode } from "../types/Mode" +import { Site } from "../types/Site" import { Title } from "../types/Title" interface FilterOption { @@ -75,11 +78,29 @@ const filters: FilterOption[] = [ }, isEnabled: (p) => p.modes.length === 1 && p.modes.includes(Mode.BULLET), }, + { + title: "On Chess.com", + Icon: PawnIcon, + enable: (p) => { + p.sites.push(Site.CHESSCOM) + return p + }, + isEnabled: (p) => p.sites.includes(Site.CHESSCOM), + }, + { + title: "On Lichess", + Icon: KnightIcon, + enable: (p) => { + p.sites.push(Site.LICHESS) + return p + }, + isEnabled: (p) => p.sites.includes(Site.LICHESS), + }, { title: "Titled Player", Icon: TrophyIcon, enable: (p) => { - p.titles = Object.keys(Title) + p.titles = Object.keys(Title) as Title[] return p }, isEnabled: (p) => p.titles.length > 0, diff --git a/assets/js/react/components/SearchResult.tsx b/assets/js/react/components/SearchResult.tsx index 7d3821e..4837dee 100644 --- a/assets/js/react/components/SearchResult.tsx +++ b/assets/js/react/components/SearchResult.tsx @@ -1,41 +1,77 @@ import * as React from "react" import clsx from "clsx" -type SearchResultProps = { - title?: string - subtitle?: string - src?: string -} & React.ComponentPropsWithoutRef<"a"> +import type { Coach } from "../types/Coach" + +import PawnIcon from "../icons/Pawn" +import KnightIcon from "../icons/Knight" + +function getSiteIcon(coach: Coach) { + switch (coach.site) { + case "chesscom": + return ({ className, ...props }: { className?: string }) => ( + + ) + case "lichess": + return ({ className, ...props }: { className?: string }) => ( + + ) + default: + return null + } +} + +function getProfileUrl(coach: Coach) { + switch (coach.site) { + case "chesscom": + return `https://www.chess.com/member/${coach.username}` + case "lichess": + return `https://lichess.org/coach/${coach.username}` + default: + return "" + } +} + +export function SearchResult({ coach }: { coach: Coach }) { + const profileUrl = getProfileUrl(coach) + const Component = profileUrl ? "a" : "div" + const Icon = getSiteIcon(coach) -export function SearchResult({ - title, - subtitle, - src, - className, - ...props -}: SearchResultProps) { return ( - + {Icon && ( +
+ +
+ )}
- {title ? ( + {coach.name ? (

- {title} + {coach.title ? ( + {coach.title} + ) : null} + {coach.name}

) : null} - {subtitle ? ( -

{subtitle}

- ) : null} +

{coach.username}

-
+ ) } diff --git a/assets/js/react/icons/Knight.tsx b/assets/js/react/icons/Knight.tsx new file mode 100644 index 0000000..d498ef0 --- /dev/null +++ b/assets/js/react/icons/Knight.tsx @@ -0,0 +1,14 @@ +import * as React from "react" + +const SvgComponent = ({ ...props }) => ( + + + +) + +export default SvgComponent diff --git a/assets/js/react/icons/Pawn.tsx b/assets/js/react/icons/Pawn.tsx new file mode 100644 index 0000000..13bddf7 --- /dev/null +++ b/assets/js/react/icons/Pawn.tsx @@ -0,0 +1,9 @@ +import * as React from "react" + +const SvgComponent = ({ ...props }) => ( + + + +) + +export default SvgComponent diff --git a/assets/js/react/pages/Search.tsx b/assets/js/react/pages/Search.tsx index 77c9598..bbeab5d 100644 --- a/assets/js/react/pages/Search.tsx +++ b/assets/js/react/pages/Search.tsx @@ -60,21 +60,9 @@ function SearchResults({ searchParams }: { searchParams: SearchParams }) { {group.map((coach) => ( - + ))} diff --git a/assets/js/react/types/Mode.ts b/assets/js/react/types/Mode.ts index 29e76f0..f24588e 100644 --- a/assets/js/react/types/Mode.ts +++ b/assets/js/react/types/Mode.ts @@ -4,6 +4,6 @@ export enum Mode { BULLET = "BULLET", } -export const getModeName = (m: Mode) => { - return m.charAt(0) + m.toLowerCase().slice(1) +export const getModeName = (mode: Mode) => { + return mode.charAt(0) + mode.toLowerCase().slice(1) } diff --git a/assets/js/react/types/SearchParams.ts b/assets/js/react/types/SearchParams.ts index a9dbf01..5761791 100644 --- a/assets/js/react/types/SearchParams.ts +++ b/assets/js/react/types/SearchParams.ts @@ -1,4 +1,5 @@ import { Mode } from "./Mode" +import { Site } from "./Site" import { Title } from "./Title" export type SearchParams = { @@ -6,6 +7,7 @@ export type SearchParams = { modes: Mode[] languages: string[] titles: Title[] + sites: Site[] } export const FIDE_RATING_MIN = 1500 @@ -16,11 +18,16 @@ export const defaultSearchParams: SearchParams = { modes: [Mode.RAPID, Mode.BLITZ, Mode.BULLET], languages: [], titles: [], + sites: [Site.CHESSCOM, Site.LICHESS], } export function toQueryParams(p: SearchParams) { const queryParams: { [key: string]: any } = {} + if (p.sites.length > 0) { + queryParams["sites"] = p.sites.join(",") + } + for (const mode of p.modes) { queryParams[`${mode.toLowerCase()}_gte`] = p.rating[0] queryParams[`${mode.toLowerCase()}_lte`] = p.rating[1] diff --git a/assets/js/react/types/Site.ts b/assets/js/react/types/Site.ts new file mode 100644 index 0000000..219e3c3 --- /dev/null +++ b/assets/js/react/types/Site.ts @@ -0,0 +1,16 @@ +export enum Site { + CHESSCOM = "chesscom", + LICHESS = "lichess", +} + +export function getSiteName(site: Site) { + switch (site) { + case Site.CHESSCOM: + return "Chess.com" + case Site.LICHESS: + return "Lichess" + default: + const _exhaustivenessCheck: never = site + return _exhaustivenessCheck + } +} diff --git a/assets/tailwind.config.cjs b/assets/tailwind.config.cjs index f616332..974e937 100644 --- a/assets/tailwind.config.cjs +++ b/assets/tailwind.config.cjs @@ -28,8 +28,8 @@ module.exports = { }, extend: { backgroundImage: { - "radial-gradient/black": - "radial-gradient(circle at center, black 0%, transparent 50%)", + "radial-gradient/gray": + "radial-gradient(circle at center, #d3d3d3 0%, transparent 100%)", }, borderRadius: { "4xl": "2.5rem", diff --git a/assets/tsconfig.json b/assets/tsconfig.json index 75c127b..0150b21 100644 --- a/assets/tsconfig.json +++ b/assets/tsconfig.json @@ -2,7 +2,7 @@ // https://esbuild.github.io/content-types/#tsconfig-json "compilerOptions": { // Keep in mind that ES6+ syntax to ES5 is not supported in esbuild yet. - "target": "es2016", + "target": "es2017", // https://www.typescriptlang.org/docs/handbook/modules/theory.html "module": "nodenext", // Even when transpiling a single module, the TypeScript compiler actually diff --git a/lib/boardwise/coaches.ex b/lib/boardwise/coaches.ex index 9103200..e9c75be 100644 --- a/lib/boardwise/coaches.ex +++ b/lib/boardwise/coaches.ex @@ -50,6 +50,7 @@ defmodule BoardWise.Coaches do :bullet_lte => bullet_lte, :languages => languages, :titles => titles, + :sites => sites, :page_no => page_no, :page_size => page_size }) do @@ -67,17 +68,20 @@ defmodule BoardWise.Coaches do ? + ? + (5 * (SELECT COUNT(*) FROM UNNEST(?) WHERE UNNEST = ANY(?))) + - CASE WHEN ? = ANY(?) THEN 5 ELSE 0 END + CASE WHEN ? = ANY(?) THEN 5 ELSE 0 END + + CASE WHEN ? = ANY(?) THEN 30 ELSE 0 END """, c.name, c.image_url, rating_fragment(c.rapid, ^rapid_gte, ^rapid_lte), rating_fragment(c.blitz, ^blitz_gte, ^blitz_lte), rating_fragment(c.bullet, ^bullet_gte, ^bullet_lte), - type(^languages, {:array, :string}), c.languages, + type(^languages, {:array, :string}), c.title, - type(^titles, {:array, :string}) + type(^titles, {:array, :string}), + c.site, + type(^sites, {:array, :string}) ) |> selected_as(:score) } diff --git a/lib/boardwise/coaches/query_params.ex b/lib/boardwise/coaches/query_params.ex index d32762c..cc49d08 100644 --- a/lib/boardwise/coaches/query_params.ex +++ b/lib/boardwise/coaches/query_params.ex @@ -8,6 +8,7 @@ defmodule BoardWise.Coaches.QueryParams do :bullet_lte, :titles, :languages, + :sites, page_no: 1, page_size: 15 ] diff --git a/lib/boardwise_web/controllers/coach_controller.ex b/lib/boardwise_web/controllers/coach_controller.ex index 7b05bd8..8e21369 100644 --- a/lib/boardwise_web/controllers/coach_controller.ex +++ b/lib/boardwise_web/controllers/coach_controller.ex @@ -15,6 +15,7 @@ defmodule BoardWiseWeb.CoachController do |> override_param(:bullet_lte, params, :integer) |> override_param(:languages, params, :strlist) |> override_param(:titles, params, :strlist) + |> override_param(:sites, params, :strlist) |> override_param(:page_no, params, :integer) |> override_param(:page_size, params, :integer)