onEnable(e.enable({ ...query }))}
+ onClick={() => onSelect(e.enable({ ...params }))}
>
{e.title}
diff --git a/assets/js/react/components/SearchResult.tsx b/assets/js/react/components/SearchResult.tsx
new file mode 100644
index 0000000..32f24fa
--- /dev/null
+++ b/assets/js/react/components/SearchResult.tsx
@@ -0,0 +1,41 @@
+import * as React from "react"
+import clsx from "clsx"
+
+type SearchResultProps = {
+ title?: string
+ subtitle?: string
+ src?: string
+} & React.ComponentPropsWithoutRef<"div">
+
+export function SearchResult({
+ title,
+ subtitle,
+ src,
+ className,
+ ...props
+}: SearchResultProps) {
+ return (
+
+
+
+ {title ? (
+
+ {title}
+
+ ) : null}
+ {subtitle ? (
+
{subtitle}
+ ) : null}
+
+
+ )
+}
diff --git a/assets/js/react/pages/Search.tsx b/assets/js/react/pages/Search.tsx
index 5efee9b..01b7ffc 100644
--- a/assets/js/react/pages/Search.tsx
+++ b/assets/js/react/pages/Search.tsx
@@ -1,63 +1,68 @@
import * as React from "react"
+import axios from "axios"
+import { useQuery } from "@tanstack/react-query"
-import type { Query } from "../types/Query"
+import type { Coach } from "../types/Coach"
+import { type SearchParams, defaultSearchParams } from "../types/SearchParams"
-import { CaptionImage } from "../components/CaptionImage"
import { Container } from "../components/Container"
import { FadeIn, FadeInStagger } from "../components/FadeIn"
import { FallbackMessage } from "../components/FallbackMessage"
import { FilterScroll } from "../components/FilterScroll"
import { Loading } from "../components/Loading"
+import { SearchResult } from "../components/SearchResult"
-const FIDE_RATING_MIN = 1500
-const FIDE_RATING_MAX = 3200
+function SearchResults() {
+ const { isLoading, isError, data } = useQuery({
+ queryKey: ["coaches"],
+ queryFn: async () => {
+ const response = await axios.get<{ data: Coach[] }>("/api/coaches/")
+ return response.data.data
+ },
+ })
-interface Coach {
- id: string
- imageUrl: string
- name: string
- title: string
- slug: string
-}
+ if (isLoading) {
+ return
+ }
-const defaultQuery: Query = {
- fideRating: [FIDE_RATING_MIN, FIDE_RATING_MAX],
+ if (isError) {
+ return (
+
+ )
+ }
+
+ return (
+
+ {data?.map((coach, index) => (
+
+
+
+ ))}
+
+ )
}
export function Search() {
- const [query, setQuery] = React.useState
(defaultQuery)
- const [loading, setLoading] = React.useState(true)
- const [coaches, setCoaches] = React.useState([])
+ const [searchParams, setSearchParams] = React.useState(defaultSearchParams)
return (
- {}} />
-
- {coaches.length > 0 ? (
-
- {coaches.map((coach, index) => (
-
-
-
- ))}
-
- ) : (
-
- )}
-
+ {}}
+ />
+
)
}
diff --git a/assets/js/react/types/Coach.ts b/assets/js/react/types/Coach.ts
new file mode 100644
index 0000000..1e18501
--- /dev/null
+++ b/assets/js/react/types/Coach.ts
@@ -0,0 +1,9 @@
+export type Coach = {
+ site: string
+ username: string
+ name: string | null
+ image_url: string | null
+ rapid: number | null
+ blitz: number | null
+ bullet: number | null
+}
diff --git a/assets/js/react/types/Query.ts b/assets/js/react/types/Query.ts
deleted file mode 100644
index 0dcb14a..0000000
--- a/assets/js/react/types/Query.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type Query = {
- fideRating: [number, number]
-}
diff --git a/assets/js/react/types/SearchParams.ts b/assets/js/react/types/SearchParams.ts
new file mode 100644
index 0000000..77204c1
--- /dev/null
+++ b/assets/js/react/types/SearchParams.ts
@@ -0,0 +1,10 @@
+export type SearchParams = {
+ fideRating: [number, number]
+}
+
+const FIDE_RATING_MIN = 1500
+const FIDE_RATING_MAX = 3200
+
+export const defaultSearchParams: SearchParams = {
+ fideRating: [FIDE_RATING_MIN, FIDE_RATING_MAX],
+}
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 0153a92..f9128d6 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.1.0",
"dependencies": {
"@mui/base": "^5.0.0-beta.25",
+ "@tanstack/react-query": "^5.12.2",
+ "axios": "^1.6.2",
"clsx": "^2.0.0",
"framer-motion": "^10.16.12",
"react": "^18.2.0",
@@ -192,6 +194,30 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.12.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.12.1.tgz",
+ "integrity": "sha512-WbZztNmKq0t6QjdNmHzezbi/uifYo9j6e2GLJkodsYaYUlzMbAp91RDyeHkIZrm7EfO4wa6Sm5sxJZm5SPlh6w==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.12.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.12.2.tgz",
+ "integrity": "sha512-BeWZu8zVFH20oRc+S/K9ADPgWjEzP/XQCGBNz5IbApUwPQAdwkQYbXODVL5AyAlWiSxhx+P2xlARPBApj2Yrog==",
+ "dependencies": {
+ "@tanstack/query-core": "5.12.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0"
+ }
+ },
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
@@ -250,6 +276,21 @@
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"devOptional": true
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
+ "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
@@ -258,12 +299,63 @@
"node": ">=6"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"devOptional": true
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.3",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+ "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/framer-motion": {
"version": "10.16.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.12.tgz",
@@ -303,6 +395,25 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -326,6 +437,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -498,6 +614,19 @@
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz",
"integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q=="
},
+ "@tanstack/query-core": {
+ "version": "5.12.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.12.1.tgz",
+ "integrity": "sha512-WbZztNmKq0t6QjdNmHzezbi/uifYo9j6e2GLJkodsYaYUlzMbAp91RDyeHkIZrm7EfO4wa6Sm5sxJZm5SPlh6w=="
+ },
+ "@tanstack/react-query": {
+ "version": "5.12.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.12.2.tgz",
+ "integrity": "sha512-BeWZu8zVFH20oRc+S/K9ADPgWjEzP/XQCGBNz5IbApUwPQAdwkQYbXODVL5AyAlWiSxhx+P2xlARPBApj2Yrog==",
+ "requires": {
+ "@tanstack/query-core": "5.12.1"
+ }
+ },
"@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
@@ -556,17 +685,60 @@
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"devOptional": true
},
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "axios": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
+ "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
+ "requires": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"clsx": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
},
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
"csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"devOptional": true
},
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+ },
+ "follow-redirects": {
+ "version": "1.15.3",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
+ "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
+ },
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
"framer-motion": {
"version": "10.16.12",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.12.tgz",
@@ -589,6 +761,19 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -611,6 +796,11 @@
}
}
},
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index 140081e..f94ddb6 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -3,6 +3,8 @@
"version": "0.1.0",
"dependencies": {
"@mui/base": "^5.0.0-beta.25",
+ "@tanstack/react-query": "^5.12.2",
+ "axios": "^1.6.2",
"clsx": "^2.0.0",
"framer-motion": "^10.16.12",
"react": "^18.2.0",
diff --git a/lib/boardwise/coach.ex b/lib/boardwise/coach.ex
deleted file mode 100644
index 04df005..0000000
--- a/lib/boardwise/coach.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule BoardWise.Coach do
- use Ecto.Schema
- import Ecto.Changeset
-
- schema "coaches" do
- field :blitz, :integer
- field :bullet, :integer
- field :rapid, :integer
- field :site, :string
- field :username, :string
-
- timestamps(type: :utc_datetime)
- end
-
- @doc false
- def changeset(coach, attrs) do
- coach
- |> cast(attrs, [:site, :username, :rapid, :blitz, :bullet])
- |> validate_required([:site, :username, :rapid, :blitz, :bullet])
- end
-end
diff --git a/lib/boardwise/coaches.ex b/lib/boardwise/coaches.ex
index 2c92183..9bb1a09 100644
--- a/lib/boardwise/coaches.ex
+++ b/lib/boardwise/coaches.ex
@@ -20,7 +20,10 @@ defmodule BoardWise.Coaches do
"""
def list_coaches do
- Repo.all(Coach, prefix: @prefix)
+ Coach
+ |> limit(6)
+ |> where(site: "lichess")
+ |> Repo.all(prefix: @prefix)
end
@doc """
diff --git a/lib/boardwise/coaches/coach.ex b/lib/boardwise/coaches/coach.ex
index 87c4e95..43f5c7c 100644
--- a/lib/boardwise/coaches/coach.ex
+++ b/lib/boardwise/coaches/coach.ex
@@ -15,18 +15,27 @@ defmodule BoardWise.Coaches.Coach do
import Ecto.Changeset
schema "export" do
+ field :site, :string
+ field :username, :string
+ field :name, :string
+ field :image_url, :string
field :blitz, :integer
field :bullet, :integer
field :rapid, :integer
-
- field :site, :string
- field :username, :string
end
@doc false
def changeset(coach, attrs) do
coach
- |> cast(attrs, [:rapid, :blitz, :bullet, :site, :username])
+ |> cast(attrs, [
+ :site,
+ :username,
+ :name,
+ :image_url,
+ :rapid,
+ :blitz,
+ :bullet
+ ])
|> validate_required([:site, :username])
|> unique_constraint(:site_username_unique, name: :site_username_unique)
end
diff --git a/lib/boardwise_web/controllers/coach_json.ex b/lib/boardwise_web/controllers/coach_json.ex
index a161e06..513f636 100644
--- a/lib/boardwise_web/controllers/coach_json.ex
+++ b/lib/boardwise_web/controllers/coach_json.ex
@@ -12,6 +12,8 @@ defmodule BoardWiseWeb.CoachJSON do
%{
site: coach.site,
username: coach.username,
+ name: coach.name,
+ image_url: coach.image_url,
rapid: coach.rapid,
blitz: coach.blitz,
bullet: coach.bullet
diff --git a/priv/repo/migrations/20231204221124_name_image_url.exs b/priv/repo/migrations/20231204221124_name_image_url.exs
new file mode 100644
index 0000000..5504802
--- /dev/null
+++ b/priv/repo/migrations/20231204221124_name_image_url.exs
@@ -0,0 +1,12 @@
+defmodule BoardWise.Repo.Migrations.NameImageUrl do
+ use Ecto.Migration
+
+ @prefix "coach_scraper"
+
+ def change do
+ alter table(:export, prefix: @prefix) do
+ add :name, :string
+ add :image_url, :string
+ end
+ end
+end