Skip to content

Commit

Permalink
refactor(leagues-frontend): Move LocationSelect to standalone component
Browse files Browse the repository at this point in the history
  • Loading branch information
alexstojda committed Jul 29, 2023
1 parent 677c8ce commit 4c135e6
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 127 deletions.
146 changes: 146 additions & 0 deletions web/app/src/components/LocationSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, {useEffect, useState} from "react";
import {AxiosError} from "axios";
import {Api, ErrorResponse, pinballMap} from "../../api";
import {ActionMeta, Select, SingleValue} from "chakra-react-select";

export type LocationOption = {
label: string
value: string
type: "pinman" | "pinmap"
pinballMapId: string
}

type LocationOptionGroup = {
label: string
options: LocationOption[]
}

const pinmanApi = new Api()
const pinballMapApi = new pinballMap.Api()

type LocationSelectProps = {
onChange: (value: LocationOption) => void
onError: (error: any) => void
value?: LocationOption
}

export function LocationSelect(props: LocationSelectProps) {
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const [locationSelectFilterValue, setLocationSelectFilterValue] = useState<string>("")
const [locationSelectIsLoading, setLocationSelectIsLoading] = useState<boolean>(false)
const [pinmapLocations, setPinmapLocations] = useState<LocationOption[]>([])
const [pinmanLocations, setPinmanLocations] = useState<LocationOption[]>([])
const [locationOptions, setLocationOptions] = useState<LocationOptionGroup[]>([])

useEffect(() => {
const locationOptions = []

if (pinmanLocations.length > 0) {
locationOptions.push({
label: 'Known Locations',
options: pinmanLocations
})
}

if (pinmapLocations.length > 0) {
locationOptions.push({
label: 'Pinball Map Locations (Create New Location)',
options: pinmapLocations.filter((location: LocationOption) => {
return pinmanLocations.find((pinmanLocation: LocationOption) => {
return pinmanLocation.pinballMapId === location.pinballMapId
}) === undefined
})
})
}

setLocationOptions(locationOptions)
}, [pinmanLocations, pinmapLocations]);

function updatePinmanLocations() {
pinmanApi.locationsApi().locationsGet().then((locations) => {
setPinmanLocations(
locations.data.locations.map((location): LocationOption => ({
label: location.name,
value: location.id,
type: "pinman",
pinballMapId: location.pinball_map_id.toString()
})
))
}).catch((e: AxiosError) => {
const err = pinmanApi.parseError(e)
console.error(err)
if (props.onError) {
props.onError(err)
}
}).finally(() => {
setLocationSelectIsLoading(false)
})
}

function updatePinballMapLocations(nameFilter: string) {
if (nameFilter.length < 4) {
return
}

pinballMapApi.locationsGet(nameFilter).then((locations) => {
setPinmapLocations(
locations.map((location): LocationOption => ({
label: location.name,
value: location.id.toString(),
type: "pinmap",
pinballMapId: location.id.toString()
})
))
}).catch((e: AxiosError) => {
const err = pinballMapApi.parseError(e)
console.error(err)
if (props.onError) {
props.onError({
detail: err.errors,
title: "Pinball Map",
})
}
}).finally(() => {
setLocationSelectIsLoading(false)
})
}

useEffect(() => {
if (locationSelectFilterValue.length < 4) {
setLocationSelectIsLoading(false)
} else {
setLocationSelectIsLoading(true)
}

if (timeoutId)
clearTimeout(timeoutId);

const newTimeoutId = setTimeout(() => {
updatePinmanLocations()
updatePinballMapLocations(locationSelectFilterValue)
}, 5000);
setTimeoutId(newTimeoutId);

return () => {
clearTimeout(newTimeoutId);
};
}, [locationSelectFilterValue]);

function onChange(newValue: SingleValue<LocationOption>, _ : ActionMeta<LocationOption>) {
if (props.onChange) {
props.onChange(newValue as LocationOption)
}
}

return (
<Select isMulti={false}
onChange={onChange}
isLoading={locationSelectIsLoading}
onInputChange={(newValue) => {
setLocationSelectFilterValue(newValue)
}}
options={locationOptions}
value={props.value}
/>
)
}
138 changes: 11 additions & 127 deletions web/app/src/pages/Leagues/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,36 @@ import {
Spacer,
Stack,
} from "@chakra-ui/react";
import React, {ReactNode, useEffect, useState} from "react";
import {Api, LeagueCreate, pinballMap, useAuth} from "../../api";
import React, {ReactNode, useState} from "react";
import {Api, ErrorResponse, LeagueCreate, pinballMap, useAuth} from "../../api";
import {slugify} from "../../helpers";
import {useNavigate} from "react-router-dom";
import {AxiosError} from "axios";
import Alert, {AlertData} from "../../components/Alert";
import {ActionMeta, Select} from "chakra-react-select";
import {LocationOption, LocationSelect} from "../../components/LocationSelect";

export type LeagueFormProps = {
mode: 'create' | 'edit'
onCancel?: () => void
}

const pinmanApi = new Api();
const pinballMapApi = new pinballMap.Api();

export default function LeagueForm(props: LeagueFormProps) {
const navigate = useNavigate();

useAuth({requireAuth: true})

const [alert, setAlert] = useState<AlertData>()

const [locationValue, setLocationValue] = useState<LocationOption>()
const [slugModified, setSlugModified] = useState<boolean>(false)
const [slugError, setSlugError] = useState<ReactNode | undefined>(undefined)

const [leagueData, setLeagueData] = useState<LeagueCreate>({
name: "",
locationId: "",
slug: "",
});


async function submitForm() {
let locationId = ""
if (locationValue!.type === "pinmap") {
Expand Down Expand Up @@ -117,128 +114,18 @@ export default function LeagueForm(props: LeagueFormProps) {
}
}

function onLocationChange(newValue: any, actionMeta: ActionMeta<any>) {
function onLocationChange(newValue: LocationOption) {
setLocationValue(newValue)
}

type LocationOption = {
label: string
value: string
type: "pinman" | "pinmap"
pinballMapId: string
}

type LocationOptionGroup = {
label: string
options: LocationOption[]
}

const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const [locationSelectFilterValue, setLocationSelectFilterValue] = useState<string>("")
const [locationSelectIsLoading, setLocationSelectIsLoading] = useState<boolean>(false)
const [pinmapLocations, setPinmapLocations] = useState<LocationOption[]>([])
const [pinmanLocations, setPinmanLocations] = useState<LocationOption[]>([])
const [locationOptions, setLocationOptions] = useState<LocationOptionGroup[]>([])
const [locationValue, setLocationValue] = useState<LocationOption>()

useEffect(() => {
const locationOptions = []

if (pinmanLocations.length > 0) {
locationOptions.push({
label: 'Known Locations',
options: pinmanLocations
})
}

if (pinmapLocations.length > 0) {
locationOptions.push({
label: 'Pinball Map Locations (Create New Location)',
options: pinmapLocations.filter((location: LocationOption) => {
return pinmanLocations.find((pinmanLocation: LocationOption) => {
return pinmanLocation.pinballMapId === location.pinballMapId
}) === undefined
})
})
}

setLocationOptions(locationOptions)
}, [pinmanLocations, pinmapLocations]);

function updatePinmanLocations() {
pinmanApi.locationsApi().locationsGet().then((locations) => {
setPinmanLocations(
locations.data.locations.map((location) : LocationOption => ({
label: location.name,
value: location.id,
type: "pinman",
pinballMapId: location.pinball_map_id.toString()
})
))
}).catch((e: AxiosError) => {
const err = pinmanApi.parseError(e)
console.error(err)
setAlert({
status: "error",
title: err.title,
detail: err.detail
})
}).finally(() => {
setLocationSelectIsLoading(false)
})
}

function updatePinballMapLocations(nameFilter: string) {
if (nameFilter.length < 4) {
return
}

pinballMapApi.locationsGet(nameFilter).then((locations) => {
setPinmapLocations(
locations.map((location): LocationOption => ({
label: location.name,
value: location.id.toString(),
type: "pinmap",
pinballMapId: location.id.toString()
})
))
}).catch((e: AxiosError) => {
const err = pinballMapApi.parseError(e)
console.error(err)
setAlert({
status: "error",
title: "Error loading locations",
detail: err.errors
})
}).finally(() => {
setLocationSelectIsLoading(false)
function onSelectError(e: ErrorResponse) {
setAlert({
status: "error",
title: e.title,
detail: e.detail
})
}

// useEffect to listen for changes in the input value
useEffect(() => {
if (locationSelectFilterValue.length < 4) {
setLocationSelectIsLoading(false)
} else {
setLocationSelectIsLoading(true)
}
// Clear the previous timeout on each input change
if (timeoutId)
clearTimeout(timeoutId);

// Start a new timeout that will call the delayedFunction after 3 seconds of inactivity
const newTimeoutId = setTimeout(() => {
updatePinmanLocations()
updatePinballMapLocations(locationSelectFilterValue)
}, 5000);
setTimeoutId(newTimeoutId);

// Cleanup the timeout on unmount to avoid memory leaks
return () => {
clearTimeout(newTimeoutId);
};
}, [locationSelectFilterValue]);

return (
<>
{alert &&
Expand Down Expand Up @@ -267,10 +154,7 @@ export default function LeagueForm(props: LeagueFormProps) {
</FormControl>
<FormControl isRequired>
<FormLabel>Location</FormLabel>
<Select isMulti={false} onChange={onLocationChange} isLoading={locationSelectIsLoading}
onInputChange={(newValue) => {
setLocationSelectFilterValue(newValue)
}} options={locationOptions}/>
<LocationSelect onChange={onLocationChange} onError={onSelectError}/>
<FormHelperText>
The location where the league takes place
</FormHelperText>
Expand Down

0 comments on commit 4c135e6

Please sign in to comment.