Setup to load in languages.

pull/3/head
Joshua Potter 2023-12-05 15:02:58 -07:00
parent 0487935038
commit ba5070be04
13 changed files with 227 additions and 4 deletions

View File

@ -6,6 +6,7 @@ import { Field } from "./FieldSet"
import { Input } from "./Input" import { Input } from "./Input"
import { Label } from "./Label" import { Label } from "./Label"
import { Modal } from "./Modal" import { Modal } from "./Modal"
// import { SelectLanguage, SelectLanguageProps } from './SelectLanguage'
import { Slider } from "./Slider" import { Slider } from "./Slider"
import { import {
@ -63,6 +64,19 @@ export function FilterModal({
// Registration // Registration
// const proxyLanguages = register('languages')
// const registerLanguages: Pick<
// SelectLanguageProps,
// 'defaultValue' | 'onChange'
// > = {
// ...proxyLanguages,
// defaultValue: defaultValues.languages,
// onChange: (event, value) => {
// event && proxyLanguages.onChange(event)
// setValue('languages', (value ?? []) as string[])
// },
// }
const controlFIDERating = register("fideRating") const controlFIDERating = register("fideRating")
return ( return (
@ -82,6 +96,25 @@ export function FilterModal({
}} }}
> >
<div className="flex flex-col gap-12"> <div className="flex flex-col gap-12">
{/*<Field>
<Label htmlFor={`${idPrefix}-languages`}>
Preferred Language(s):
</Label>
<p className="py-2 text-sm">
Select languages you prefer communicating in. We{"'"}ll prioritize
finding coaches that can speak fluently in at least one of your
selections.
</p>
<SelectLanguage
id={`${idPrefix}-languages`}
slotProps={{
root: { className: 'w-full' },
}}
{...registerLanguages}
multiple
/>
</Field>*/}
<Field> <Field>
<Label htmlFor={`${idPrefix}-fideRating`}>FIDE Rating:</Label> <Label htmlFor={`${idPrefix}-fideRating`}>FIDE Rating:</Label>
<p className="py-2 text-sm"> <p className="py-2 text-sm">

View File

@ -4,6 +4,7 @@ import clsx from "clsx"
import type { SearchParams } from "../types/SearchParams" import type { SearchParams } from "../types/SearchParams"
import FilterIcon from "../icons/Filter" import FilterIcon from "../icons/Filter"
import EnglishIcon from "../icons/English"
import RightArrowIcon from "../icons/RightArrow" import RightArrowIcon from "../icons/RightArrow"
import RisingGraphIcon from "../icons/RisingGraph" import RisingGraphIcon from "../icons/RisingGraph"
import { Button } from "./Button" import { Button } from "./Button"
@ -19,11 +20,27 @@ const filters: FilterOption[] = [
{ {
title: "FIDE 2000+", title: "FIDE 2000+",
Icon: RisingGraphIcon, Icon: RisingGraphIcon,
enable: (q) => { enable: (p) => {
q.fideRating[0] = Math.max(2000, q.fideRating[0]) p.fideRating[0] = Math.max(2000, p.fideRating[0])
return q return p
}, },
isEnabled: (q) => q.fideRating[0] >= 2000, isEnabled: (p) => p.fideRating[0] >= 2000,
},
{
title: "English Speaking",
Icon: EnglishIcon,
enable: (p) => {
for (const lang of ["en-US", "en-GB"]) {
if (!p.languages.includes(lang)) {
p.languages.push(lang)
}
}
return p
},
// Using `||` doesn't match how `enable` works but this is probably closer
// to how people would expect the filter to operate.
isEnabled: (p) =>
p.languages.includes("en-US") || p.languages.includes("en-GB"),
}, },
] ]

View File

@ -0,0 +1,105 @@
import * as React from "react"
import clsx from "clsx"
import {
Select as BaseSelect,
SelectProps,
SelectOwnerState,
} from "@mui/base/Select"
import {
Option as BaseOption,
OptionProps,
OptionOwnerState,
} from "@mui/base/Option"
import { FieldContext } from "./FieldSet"
import { resolveSlotProps } from "../utils/props"
import { sameWidth } from "../utils/popperjs"
export const Option = React.forwardRef<HTMLLIElement, OptionProps<string>>(
function Option(props, ref) {
const rootSlotProps = (ownerState: OptionOwnerState<string>) => {
const resolved = resolveSlotProps(props.slotProps?.root, ownerState)
return {
...resolved,
className: clsx(
"list-none p-2 rounded-lg cursor-default last-of-type:border-b-0",
ownerState.disabled
? "text-slate-400"
: "hover:bg-slate-100 hover:text-slate-900",
{
"font-bold": ownerState.selected,
"bg-slate-100 text-slate-900":
ownerState.selected || ownerState.highlighted,
},
resolved?.className
),
}
}
return (
<BaseOption ref={ref} {...props} slotProps={{ root: rootSlotProps }} />
)
}
)
export const Select = React.forwardRef(function Select<
TValue extends {},
Multiple extends boolean,
>(
props: SelectProps<TValue, Multiple>,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const fieldContext = React.useContext(FieldContext)
const { disabled = fieldContext?.disabled, slotProps, ...other } = props
const rootSlotProps = (ownerState: SelectOwnerState<TValue, Multiple>) => {
const resolved = resolveSlotProps(slotProps?.root, ownerState)
return {
...resolved,
className: clsx(
"text-sm box-border px-3 py-2 rounded-lg text-left bg-white border border-solid text-slate-900 transition-all hover:bg-slate-50 outline-0 shadow shadow-slate-100 after:float-right",
ownerState.open ? 'after:content-["▴"]' : 'after:content-["▾"]',
ownerState.disabled ? "opacity-60" : "",
fieldContext?.error
? "border-amber-800 focus-visible:outline focus-visible:outline-1 focus-visible:outline-amber-800"
: "border-slate-300",
resolved?.className
),
}
}
const listboxSlotProps = (ownerState: SelectOwnerState<TValue, Multiple>) => {
const resolved = resolveSlotProps(slotProps?.listbox, ownerState)
return {
...resolved,
className: clsx(
"text-sm p-1.5 my-3 rounded-xl h-60 overflow-auto outline-0 bg-white border border-solid border-slate-200 text-slate-900 shadow shadow-slate-100",
resolved?.className
),
}
}
const popperSlotProps = (ownerState: SelectOwnerState<TValue, Multiple>) => {
const resolved = resolveSlotProps(slotProps?.popper, ownerState)
return {
...resolved,
className: clsx("z-[1000]", resolved?.className),
modifiers: [sameWidth],
}
}
return (
<BaseSelect
ref={ref}
{...other}
slotProps={{
...slotProps,
root: rootSlotProps,
listbox: listboxSlotProps,
popper: popperSlotProps,
}}
disabled={disabled}
/>
)
})

View File

@ -0,0 +1,30 @@
import * as React from "react"
const SvgComponent = ({ ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 48 48"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={4}
d="M13 31V17h8M13 24h7.5M13 31h7.5M26 31V19M26 31v-6.5a4.5 4.5 0 0 1 4.5-4.5v0a4.5 4.5 0 0 1 4.5 4.5V31"
/>
<rect
width={36}
height={36}
x={6}
y={6}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={4}
rx={3}
/>
</svg>
)
export default SvgComponent

View File

@ -3,6 +3,7 @@ export type Coach = {
username: string username: string
name: string | null name: string | null
image_url: string | null image_url: string | null
languages: string[] | null
rapid: number | null rapid: number | null
blitz: number | null blitz: number | null
bullet: number | null bullet: number | null

View File

@ -0,0 +1,4 @@
export type Language = {
name: string
code: string
}

View File

@ -1,5 +1,6 @@
export type SearchParams = { export type SearchParams = {
fideRating: [number, number] fideRating: [number, number]
languages: string[]
} }
export const FIDE_RATING_MIN = 1500 export const FIDE_RATING_MIN = 1500
@ -7,4 +8,5 @@ export const FIDE_RATING_MAX = 3200
export const defaultSearchParams: SearchParams = { export const defaultSearchParams: SearchParams = {
fideRating: [FIDE_RATING_MIN, FIDE_RATING_MAX], fideRating: [FIDE_RATING_MIN, FIDE_RATING_MAX],
languages: [],
} }

View File

@ -0,0 +1,16 @@
import { type Modifier } from "@popperjs/core"
export const sameWidth: Partial<Modifier<any, any>> = {
name: "sameWidth",
phase: "beforeWrite",
enabled: true,
requires: ["computeStyles"],
fn: ({ state }) => {
state.styles.popper.width = `${state.rects.reference.width}px`
},
effect: ({ state }) => {
if ("offsetWidth" in state.elements.reference) {
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`
}
},
}

View File

@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@mui/base": "^5.0.0-beta.25", "@mui/base": "^5.0.0-beta.25",
"@popperjs/core": "^2.11.8",
"@tanstack/react-query": "^5.12.2", "@tanstack/react-query": "^5.12.2",
"axios": "^1.6.2", "axios": "^1.6.2",
"clsx": "^2.0.0", "clsx": "^2.0.0",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@mui/base": "^5.0.0-beta.25", "@mui/base": "^5.0.0-beta.25",
"@popperjs/core": "^2.11.8",
"@tanstack/react-query": "^5.12.2", "@tanstack/react-query": "^5.12.2",
"axios": "^1.6.2", "axios": "^1.6.2",
"clsx": "^2.0.0", "clsx": "^2.0.0",

View File

@ -19,6 +19,7 @@ defmodule BoardWise.Coaches.Coach do
field :username, :string field :username, :string
field :name, :string field :name, :string
field :image_url, :string field :image_url, :string
field :languages, {:array, :string}
field :blitz, :integer field :blitz, :integer
field :bullet, :integer field :bullet, :integer
field :rapid, :integer field :rapid, :integer

View File

@ -14,6 +14,7 @@ defmodule BoardWiseWeb.CoachJSON do
username: coach.username, username: coach.username,
name: coach.name, name: coach.name,
image_url: coach.image_url, image_url: coach.image_url,
languages: coach.languages,
rapid: coach.rapid, rapid: coach.rapid,
blitz: coach.blitz, blitz: coach.blitz,
bullet: coach.bullet bullet: coach.bullet

View File

@ -0,0 +1,11 @@
defmodule BoardWise.Repo.Migrations.Languages do
use Ecto.Migration
@prefix "coach_scraper"
def change do
alter table(:export, prefix: @prefix) do
add :languages, {:array, :string}
end
end
end