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({
}}
>
+
+
+
+
@@ -208,22 +244,6 @@ export function FilterModal({
-
-
)
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)