Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This app allows to configure the `schedule` and `person` fields in the wcif. For
## Project Information

- **Type**: Single-page web application (React SPA)
- **Size**: ~110 source files (33 JSX, 33 TSX, 23 TS, 15 JS)
- **Size**: ~110 source files (All typescript)
- **Languages**: TypeScript, JavaScript (JSX/TSX), HTML, CSS
- **Frameworks**: React 17, Redux, Material-UI v5, Vite
- **Build Tool**: Vite 4.4.9 (migrated from Create React App)
Expand Down Expand Up @@ -75,6 +75,7 @@ yarn test
### Pre-commit Hook

The repository uses Husky to run tests before commits:

- Located at: `.husky/pre-commit`
- Runs: `CI=true npm test` (which runs `npm run lint`)
- This will block commits if linting fails
Expand Down Expand Up @@ -168,19 +169,24 @@ Before committing changes, always:
## Common Issues & Workarounds

### Issue 1: npm install fails with peer dependency errors

**Solution**: Use `yarn install` instead, or `npm install --legacy-peer-deps`

### Issue 2: TypeScript compiler (tsc) not found

**Cause**: Using npm without installing TypeScript properly
**Solution**: Use `yarn` instead, which properly installs all dependencies

### Issue 3: Build fails with "Cannot find module" errors
**Solution**:

**Solution**:

1. Delete `node_modules/` and `yarn.lock`/`package-lock.json`
2. Run `yarn install` (fresh install)
3. Run `yarn build`

### Issue 4: Git commit blocked by pre-commit hook

**Cause**: ESLint found errors in code
**Solution**: Run `yarn lint` to see errors, fix them, then commit again

Expand Down Expand Up @@ -224,6 +230,7 @@ yarn.lock # Yarn dependency lock file
## Trust These Instructions

The information in this file has been validated by running actual commands against the repository. Only search for additional information if:

- You encounter an error not documented here
- You need to understand specific business logic in the code
- The instructions appear outdated (check git commit dates)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"scripts": {
"start": "VITE_GIT_SHA=`git rev-parse --short HEAD` GENERATE_SOURCEMAP=false vite",
"build": "VITE_GIT_SHA=`git rev-parse --short HEAD` tsc && vite build",
"lint": "eslint ./",
"lint": "eslint",
"test": "npm run lint",
"prepare": "husky install",
"prettier": "prettier \"src/**/*.js\" --write"
Expand Down
2 changes: 1 addition & 1 deletion src/App/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const links = [
},
];

const Footer = () => {
const Footer = (props?: any) => {
return (
<Grid container sx={{ p: 2 }}>
<Grid item>
Expand Down
2 changes: 1 addition & 1 deletion src/App/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Alert, AlertTitle, Box } from '@mui/material';
import { useAuth } from '../providers/AuthProvider';
import Footer from './Footer';

const App = () => {
const App = (props?: any) => {
const { userFetchError } = useAuth();

return (
Expand Down
6 changes: 3 additions & 3 deletions src/App/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Layout from './Layout';
import { useEffect } from 'react';
import { Routes, Route, Outlet, Navigate, useNavigate, useParams } from 'react-router-dom';

const AuthenticatedRoute = () => {
const AuthenticatedRoute = (props?: any) => {
const { signIn, signedIn } = useAuth();

if (!signedIn()) {
Expand All @@ -33,7 +33,7 @@ const AuthenticatedRoute = () => {
return <Outlet />;
};

const Comp404 = () => {
const Comp404 = (props?: any) => {
const navigate = useNavigate();
const { competitionId } = useParams();

Expand All @@ -44,7 +44,7 @@ const Comp404 = () => {
return null;
};

const Navigation = () => {
const Navigation = (props?: any) => {
usePageTracking(import.meta.env.VITE_GA_MEASUREMENT_ID);

return (
Expand Down
2 changes: 1 addition & 1 deletion src/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAuth } from '../providers/AuthProvider';
import { fetchCompetitions } from '../store/actions';
import Navigation from './Navigation';

const App = () => {
const App = (props?: any) => {
const dispatch = useDispatch();
const { user } = useAuth();

Expand Down
2 changes: 1 addition & 1 deletion src/components/ActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function ActionMenu({items}: ActionMenuProps) {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
const handleClose = (props?: any) => {
setAnchorEl(null);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Fuse from 'fuse.js';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import useDebounce from '../../hooks/useDebounce';
import { findAllActivities } from '../../lib/activities';
import { acceptedRegistrations } from '../../lib/persons';
import { AppState } from '../../store/initialState';
import SearchResultList from '../SearchResultList';
import SearchIcon from '@mui/icons-material/Search';
import {
Box,
Expand All @@ -12,33 +13,35 @@ import {
Paper,
} from '@mui/material';
import { useTheme } from '@mui/styles';
import useDebounce from '../../hooks/useDebounce';
import { findAllActivities } from '../../lib/activities';
import { acceptedRegistrations } from '../../lib/persons';
import SearchResultList from '../SearchResultList';
import Fuse from 'fuse.js';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';

const options = {
keys: ['name', 'wcaId', 'activityCode'],
minMatchCharLength: 3,
includeScore: true,
};

function CommandPromptDialog({ open, onClose }) {
const wcif = useSelector((state) => state.wcif);
const competitions = useSelector((state) => state.competitions);
const [currentCompetitionId, setCurrentCompetitionId] = useState(wcif?.id);
function CommandPromptDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
const wcif = useSelector((state: AppState) => state.wcif);
const competitions = useSelector((state: AppState) => state.competitions);
const [currentCompetitionId, setCurrentCompetitionId] = useState<string | null>(wcif?.id ?? null);
const theme = useTheme();
const navigate = useNavigate();
const [command, setCommand] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [searchResults, setSearchResults] = useState<any[]>([]);
const [selected, setSelected] = useState(0);

useEffect(() => {
setCurrentCompetitionId(wcif.id);
if (wcif) {
setCurrentCompetitionId(wcif.id);
}
}, [wcif]);

const persons = useMemo(() => acceptedRegistrations(wcif.persons), [wcif]);
const activities = useMemo(() => findAllActivities(wcif), [wcif]);
const persons = useMemo(() => acceptedRegistrations(wcif?.persons || []), [wcif]);
const activities = useMemo(() => (wcif ? findAllActivities(wcif) : []), [wcif]);

const fuse = useMemo(() => {
if (currentCompetitionId && wcif) {
Expand Down Expand Up @@ -75,7 +78,7 @@ function CommandPromptDialog({ open, onClose }) {

useEffect(() => {
if (debouncedCommand) {
setSearchResults(fuse.search(debouncedCommand).filter(({ score }) => score < 1));
setSearchResults(fuse.search(debouncedCommand).filter(({ score }) => score && score < 1));
} else {
if (!currentCompetitionId) {
setSearchResults(
Expand All @@ -100,10 +103,10 @@ function CommandPromptDialog({ open, onClose }) {
(result) => {
switch (result.class) {
case 'person':
navigate(`/competitions/${wcif.id}/persons/${result.id}`);
navigate(`/competitions/${wcif?.id}/persons/${result.id}`);
break;
case 'activity':
navigate(`/competitions/${wcif.id}/events/${result.activityCode}`);
navigate(`/competitions/${wcif?.id}/events/${result.activityCode}`);
break;
case 'competition':
navigate(`/competitions/${result.id}`);
Expand All @@ -114,7 +117,7 @@ function CommandPromptDialog({ open, onClose }) {

handleClose();
},
[handleClose, navigate, wcif.id]
[handleClose, navigate, wcif?.id]
);

const handleKeyDown = useCallback(
Expand Down Expand Up @@ -153,6 +156,7 @@ function CommandPromptDialog({ open, onClose }) {
<ClickAwayListener onClickAway={handleClose}>
<Box
style={{
// @ts-expect-error TODO: Fix issues with MUI types
zIndex: theme.zIndex.drawer * 10,
top: open ? 0 : '-100%',
left: open ? '25%' : '50%',
Expand All @@ -161,9 +165,12 @@ function CommandPromptDialog({ open, onClose }) {

position: 'fixed',
// display: open ? 'block' : 'none',
// @ts-expect-error TODO: Fix issues with MUI types
border: theme.palette.divider,
// @ts-expect-error TODO: Fix issues with MUI types
transition: theme.transitions.create(['top', 'left', 'width']),
overflow: 'hidden',
// @ts-expect-error TODO: Fix issues with MUI types
boxShadow: theme.shadows[6],
}}>
<Paper
Expand Down Expand Up @@ -209,6 +216,7 @@ function CommandPromptDialog({ open, onClose }) {
display: 'flex',
flex: 1,
flexDirection: 'column',
// @ts-expect-error TODO: Fix issues with MUI types
transition: theme.transitions.create(['top', 'left', 'width']),
}}>
{searchResults?.length ? (
Expand Down
4 changes: 2 additions & 2 deletions src/components/CompetitionLists/CompetitionLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface APICompetition {
}

// https://github.com/thewca/wca-live/blob/8884f8dc5bb2efcc3874f9fff4f6f3c098efbd6a/client/src/lib/date.js#L10
const formatDateRange = (startString, endString) => {
const formatDateRange = (startString: string, endString: string): string => {
const [startDay, startMonth, startYear] = format(parseISO(startString), 'd MMM yyyy').split(' ');
const [endDay, endMonth, endYear] = format(parseISO(endString), 'd MMM yyyy').split(' ');
if (startString === endString) {
Expand All @@ -27,7 +27,7 @@ const formatDateRange = (startString, endString) => {
return `${startMonth} ${startDay}, ${startYear} - ${endMonth} ${endDay}, ${endYear}`;
};

export const CompetitionLink = ({ comp }) => (
export const CompetitionLink = ({ comp }: { comp: APICompetition }) => (
<ListItemButton component={Link} to={`/competitions/${comp.id}`}>
<ListItemIcon>
{!comp.country_iso2 || RegExp('(x|X)', 'g').test(comp.country_iso2.toLowerCase()) ? (
Expand Down
2 changes: 1 addition & 1 deletion src/components/CompetitionLists/PastCompetitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { APICompetition, CompetitionLink } from './CompetitionLink';
import { Container } from '@mui/material';
import { useEffect, useState } from 'react';

export const PastCompetitions = () => {
export const PastCompetitions = (props?: any) => {
const [competitions, setCompetitions] = useState<APICompetition[] | null>(null);
const [error, setError] = useState<Error | null>(null);

Expand Down
2 changes: 1 addition & 1 deletion src/components/CompetitionLists/UpcomingCompetitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { APICompetition, CompetitionLink } from './CompetitionLink';
import { Container } from '@mui/material';
import { useEffect, useState } from 'react';

export const UpcomingCompetitions = () => {
export const UpcomingCompetitions = (props?: any) => {
const [competitions, setCompetitions] = useState<APICompetition[] | null>(null);
const [error, setError] = useState<Error | null>(null);

Expand Down
8 changes: 4 additions & 4 deletions src/components/CompetitionSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { shortEventNameById } from '../lib/events';
import { acceptedRegistrations } from '../lib/persons';
import { Card, CardContent, Typography } from '@mui/material';
import { Competition } from '@wca/helpers';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { Card, CardContent, Typography } from '@mui/material';
import { shortEventNameById } from '../lib/events';
import { acceptedRegistrations } from '../lib/persons';

export default function CompetitionSummary() {
export default function CompetitionSummary(props?: any) {
const wcif = useSelector((state: { wcif: Competition }) => state.wcif);
const approvedRegistrations = acceptedRegistrations(wcif.persons);

Expand Down
7 changes: 1 addition & 6 deletions src/components/Errors/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
export interface WCIFError {
type: string;
key: string;
message: string;
data: any;
}
import { WCIFError } from '../../store/initialState';

export interface ErrorsProps {
errors: WCIFError[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import PropTypes from 'prop-types';
import { IconButton } from '@mui/material';
import { makeStyles } from '@mui/styles';
import { EventId } from '@wca/helpers';

const useStyles = makeStyles(() => ({
root: {
Expand All @@ -17,12 +17,19 @@ const useStyles = makeStyles(() => ({
},
}));

interface EventSelectorProps {
eventIds: EventId[];
value: EventId[];
onChange: (eventIds: EventId[]) => void;
styleOverrides?: {
root?: React.CSSProperties;
};
}

/**
* Shows a list of events and allows the user to checkbox select the events
*
* @component
*/
const EventSelector = ({ eventIds, value, onChange, styleOverrides }) => {
const EventSelector = ({ eventIds, value, onChange, styleOverrides }: EventSelectorProps) => {
const classes = useStyles();
return (
<div className={classes.root} style={styleOverrides?.root}>
Expand All @@ -49,28 +56,4 @@ const EventSelector = ({ eventIds, value, onChange, styleOverrides }) => {
);
};

EventSelector.propTypes = {
events: PropTypes.arrayOf(
PropTypes.oneOf([
'333',
'222',
'444',
'555',
'666',
'777',
'333bf',
'333fm',
'333oh',
'minx',
'pyram',
'clock',
'skewb',
'sq1',
'444bf',
'555bf',
'333mbf',
])
),
};

export default EventSelector;
Loading