@@ -10,7 +10,7 @@ declare global {
|
|
10
10
|
* @param dataCyAttribute The value of the data-cy attribute to get.
|
11
11
|
* @param args Optional args to pass to cy.get()
|
12
12
|
*/
|
13
|
-
getBySel(dataCyAttribute: string, args?: any): Chainable<JQuery
|
13
|
+
getBySel(dataCyAttribute: string, args?: any): Chainable<JQuery>;
|
14
14
|
|
15
15
|
/**
|
16
16
|
* Finds an element based on its data-cy attribute.
|
@@ -20,7 +20,7 @@ declare global {
|
|
20
20
|
findBySel(
|
21
21
|
dataCyAttribute: string,
|
22
22
|
args?: any
|
23
|
-
): Chainable<JQuery
|
23
|
+
): Chainable<JQuery>;
|
24
24
|
|
25
25
|
/**
|
26
26
|
* Logs into AWS Cognito via the Amplify Auth API, bypassing the login screen.
|
@@ -32,7 +32,7 @@ declare global {
|
|
32
32
|
sessionId: string,
|
33
33
|
email: string,
|
34
34
|
password: string
|
35
|
-
): Chainable
|
35
|
+
): Chainable;
|
36
36
|
|
37
37
|
/**
|
38
38
|
* Intercepts a request to the site's API.
|
@@ -60,7 +60,7 @@ Cypress.Commands.add('loginByCognitoApi', (sessionId, email, password) => {
|
|
60
60
|
cy.session(
|
61
61
|
`${sessionId}-${email}`,
|
62
62
|
() => {
|
63
|
-
|
63
|
+
loginToCognito(email, password);
|
64
64
|
},
|
65
65
|
{
|
66
66
|
validate() {
|
@@ -253,7 +253,7 @@ function App() {
|
|
253
253
|
<ThemeProvider>
|
254
254
|
<LocalizationProvider
|
255
255
|
dateAdapter={AdapterLuxon}
|
256
|
-
adapterLocale={navigator.languages
|
256
|
+
adapterLocale={navigator.languages[0]}
|
257
257
|
>
|
258
258
|
<RouterProvider router={router} />
|
259
259
|
</LocalizationProvider>
|
@@ -10,7 +10,7 @@ interface ErrorBoundaryState {
|
|
10
10
|
info: any;
|
11
11
|
}
|
12
12
|
|
13
|
-
class ErrorBoundary extends Component<any, ErrorBoundaryState
|
13
|
+
class ErrorBoundary extends Component<any, ErrorBoundaryState> {
|
14
14
|
constructor(props: any) {
|
15
15
|
super(props);
|
16
16
|
this.state = { hasError: false, error: null, info: null };
|
@@ -50,7 +50,7 @@ export function useLightMode(): boolean {
|
|
50
50
|
export function useWindowSizeEffect(handler: () => void) {
|
51
51
|
useEffect(() => {
|
52
52
|
window.addEventListener('resize', handler);
|
53
|
-
return () => window.removeEventListener('resize', handler);
|
53
|
+
return () => { window.removeEventListener('resize', handler); };
|
54
54
|
}, [handler]);
|
55
55
|
}
|
56
56
|
|
@@ -186,7 +186,7 @@ export function useApi() {
|
|
186
186
|
*/
|
187
187
|
export function ApiProvider({ children }: { children: ReactNode }) {
|
188
188
|
const auth = useAuth();
|
189
|
-
const idToken = auth.user?.cognitoUser?.session
|
189
|
+
const idToken = auth.user?.cognitoUser?.session.idToken.jwtToken ?? '';
|
190
190
|
|
191
191
|
const value = useMemo(() => {
|
192
192
|
return {
|
@@ -132,12 +132,12 @@ export function RequestSnackbar<T = any>({
|
|
132
132
|
defaultErrorMessage,
|
133
133
|
defaultSuccessMessage,
|
134
134
|
}: RequestSnackbarProps<T>) {
|
135
|
-
|
135
|
+
const displayError =
|
136
136
|
(showError === undefined || showError) &&
|
137
137
|
request.status === RequestStatus.Failure &&
|
138
138
|
request.error !== undefined;
|
139
139
|
|
140
|
-
|
140
|
+
const displaySuccess = showSuccess && request.data !== undefined;
|
141
141
|
|
142
142
|
let errorMessage =
|
143
143
|
request.error?.response?.data?.message ||
|
@@ -119,7 +119,7 @@ function useIdentifiableCache<T>(key?: string): IdentifiableCache<T> {
|
|
119
119
|
/**
|
120
120
|
* CacheContextType defines the type of the cache as available through CacheProvider
|
121
121
|
*/
|
122
|
-
|
122
|
+
interface CacheContextType {
|
123
123
|
isLoading: boolean;
|
124
124
|
setIsLoading: (arg: boolean) => void;
|
125
125
|
|
@@ -131,7 +131,7 @@ type CacheContextType = {
|
|
131
131
|
|
132
132
|
imageBypass: number;
|
133
133
|
setImageBypass: (v: number) => void;
|
134
|
-
}
|
134
|
+
}
|
135
135
|
|
136
136
|
const CacheContext = createContext<CacheContextType>(null!);
|
137
137
|
|
@@ -8,13 +8,13 @@ import { User } from '../database/user';
|
|
8
8
|
const BASE_URL = getConfig().api.baseUrl;
|
9
9
|
|
10
10
|
/** Provides an API for interacting with clubs. */
|
11
|
-
export
|
11
|
+
export interface ClubApiContextType {
|
12
12
|
/**
|
13
13
|
* Creates the given club.
|
14
14
|
* @param club The club to create.
|
15
15
|
* @returns An AxiosResponse containing the created club.
|
16
16
|
*/
|
17
|
-
createClub: (club: Partial<Club>) => Promise<AxiosResponse<ClubDetails
|
17
|
+
createClub: (club: Partial<Club>) => Promise<AxiosResponse<ClubDetails>>;
|
18
18
|
|
19
19
|
/**
|
20
20
|
* Updates the club with the given id.
|
@@ -25,7 +25,7 @@ export type ClubApiContextType = {
|
|
25
25
|
updateClub: (
|
26
26
|
id: string,
|
27
27
|
update: Partial<Club>,
|
28
|
-
) => Promise<AxiosResponse<ClubDetails
|
28
|
+
) => Promise<AxiosResponse<ClubDetails>>;
|
29
29
|
|
30
30
|
/**
|
31
31
|
* Fetches the full list of clubs in the database.
|
@@ -43,7 +43,7 @@ export type ClubApiContextType = {
|
|
43
43
|
getClub: (
|
44
44
|
id: string,
|
45
45
|
scoreboard?: boolean,
|
46
|
-
) => Promise<AxiosResponse<GetClubResponse
|
46
|
+
) => Promise<AxiosResponse<GetClubResponse>>;
|
47
47
|
|
48
48
|
/**
|
49
49
|
* Fetches the clubs with the given ids.
|
@@ -59,7 +59,7 @@ export type ClubApiContextType = {
|
|
59
59
|
* @returns An AxiosResponse containing the club's updated details and
|
60
60
|
* additional scoreboard entries.
|
61
61
|
*/
|
62
|
-
joinClub: (id: string) => Promise<AxiosResponse<GetClubResponse
|
62
|
+
joinClub: (id: string) => Promise<AxiosResponse<GetClubResponse>>;
|
63
63
|
|
64
64
|
/**
|
65
65
|
* Sends a request to join the given club.
|
@@ -70,7 +70,7 @@ export type ClubApiContextType = {
|
|
70
70
|
requestToJoinClub: (
|
71
71
|
id: string,
|
72
72
|
notes: string,
|
73
|
-
) => Promise<AxiosResponse<ClubDetails
|
73
|
+
) => Promise<AxiosResponse<ClubDetails>>;
|
74
74
|
|
75
75
|
/**
|
76
76
|
* Applies the given status to the given club join request.
|
@@ -83,15 +83,15 @@ export type ClubApiContextType = {
|
|
83
83
|
clubId: string,
|
84
84
|
username: string,
|
85
85
|
status: ClubJoinRequestStatus,
|
86
|
-
) => Promise<AxiosResponse<GetClubResponse
|
86
|
+
) => Promise<AxiosResponse<GetClubResponse>>;
|
87
87
|
|
88
88
|
/**
|
89
89
|
* Leaves the club with the given id.
|
90
90
|
* @param clubId The id of the club to leave.
|
91
91
|
* @returns An AxiosResponse containing the club's updated details.
|
92
92
|
*/
|
93
|
-
leaveClub: (clubId: string) => Promise<AxiosResponse<ClubDetails
|
94
|
-
}
|
93
|
+
leaveClub: (clubId: string) => Promise<AxiosResponse<ClubDetails>>;
|
94
|
+
}
|
95
95
|
|
96
96
|
/**
|
97
97
|
* Creates the given club.
|
@@ -129,7 +129,7 @@ interface ListClubsResponse {
|
|
129
129
|
* @returns A list of all clubs in the database.
|
130
130
|
*/
|
131
131
|
export async function listClubs(startKey?: string) {
|
132
|
-
|
132
|
+
const params = { startKey };
|
133
133
|
const result: Club[] = [];
|
134
134
|
|
135
135
|
do {
|
@@ -8,7 +8,7 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
8
8
|
/**
|
9
9
|
* CourseApiContextType provides an API for interacting with Courses.
|
10
10
|
*/
|
11
|
-
export
|
11
|
+
export interface CourseApiContextType {
|
12
12
|
/**
|
13
13
|
* getCourse returns the course with the provided type and id.
|
14
14
|
* @param type The type of the course.
|
@@ -57,8 +57,8 @@ export type CourseApiContextType = {
|
|
57
57
|
* @param course The Course to save.
|
58
58
|
* @returns An AxiosResponse containing the Course as saved in the database.
|
59
59
|
*/
|
60
|
-
setCourse: (course: Course) => Promise<AxiosResponse<Course
|
61
|
-
}
|
60
|
+
setCourse: (course: Course) => Promise<AxiosResponse<Course>>;
|
61
|
+
}
|
62
62
|
|
63
63
|
/** A response to a getCourse request. */
|
64
64
|
export interface GetCourseResponse {
|
@@ -172,11 +172,11 @@ export function purchaseCourse(
|
|
172
172
|
purchaseOption?: string,
|
173
173
|
cancelUrl?: string
|
174
174
|
) {
|
175
|
-
|
175
|
+
const url = idToken
|
176
176
|
? `${BASE_URL}/courses/${type}/${id}/purchase`
|
177
177
|
: `${BASE_URL}/public/courses/${type}/${id}/purchase`;
|
178
178
|
|
179
|
-
|
179
|
+
const headers = idToken
|
180
180
|
? {
|
181
181
|
Authorization: 'Bearer ' + idToken,
|
182
182
|
}
|
@@ -4,11 +4,11 @@ import { getConfig } from '../config';
|
|
4
4
|
|
5
5
|
const BASE_URL = getConfig().api.baseUrl;
|
6
6
|
|
7
|
-
export
|
7
|
+
export interface EmailApiContextType {
|
8
8
|
createSupportTicket: (
|
9
9
|
request: SupportTicketRequest,
|
10
10
|
) => Promise<AxiosResponse<SupportTicketResponse>>;
|
11
|
-
}
|
11
|
+
}
|
12
12
|
|
13
13
|
export interface SupportTicketRequest {
|
14
14
|
name: string;
|
@@ -9,7 +9,7 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
9
9
|
/**
|
10
10
|
* EventApiContextType provides an API for interacting with Events.
|
11
11
|
*/
|
12
|
-
export
|
12
|
+
export interface EventApiContextType {
|
13
13
|
/**
|
14
14
|
* bookEvent books the provided Event. If the Event is 1 on 1, then startTime
|
15
15
|
* and type can also be included in the request.
|
@@ -22,35 +22,35 @@ export type EventApiContextType = {
|
|
22
22
|
id: string,
|
23
23
|
startTime?: Date,
|
24
24
|
type?: string
|
25
|
-
) => Promise<AxiosResponse<BookEventResponse
|
25
|
+
) => Promise<AxiosResponse<BookEventResponse>>;
|
26
26
|
|
27
27
|
/**
|
28
28
|
* Returns a Stripe Checkout URL for the given Event. The caller must already be a particpiant of the Event.
|
29
29
|
* @param id The id of the Event to get the Checkout URL for.
|
30
30
|
* @returns The Stripe Checkout URL for the Event.
|
31
31
|
*/
|
32
|
-
getEventCheckout: (id: string) => Promise<AxiosResponse<CheckoutEventResponse
|
32
|
+
getEventCheckout: (id: string) => Promise<AxiosResponse<CheckoutEventResponse>>;
|
33
33
|
|
34
34
|
/**
|
35
35
|
* cancelEvent cancels the Event with the provided id.
|
36
36
|
* @param id The Event id to cancel.
|
37
37
|
* @returns An AxiosReponse containing the updated Event.
|
38
38
|
*/
|
39
|
-
cancelEvent: (id: string) => Promise<AxiosResponse<Event
|
39
|
+
cancelEvent: (id: string) => Promise<AxiosResponse<Event>>;
|
40
40
|
|
41
41
|
/**
|
42
42
|
* deleteEvent deletes the provided Event from the database.
|
43
43
|
* @param id The id of the Event to delete.
|
44
44
|
* @returns An AxiosResponse containing the deleted Event.
|
45
45
|
*/
|
46
|
-
deleteEvent: (id: string) => Promise<AxiosResponse<Event
|
46
|
+
deleteEvent: (id: string) => Promise<AxiosResponse<Event>>;
|
47
47
|
|
48
48
|
/**
|
49
49
|
* getEvent returns the Event with the provided id.
|
50
50
|
* @param id The Event id to fetch.
|
51
51
|
* @returns An AxiosResponse containing the Event.
|
52
52
|
*/
|
53
|
-
getEvent: (id: string) => Promise<AxiosResponse<Event
|
53
|
+
getEvent: (id: string) => Promise<AxiosResponse<Event>>;
|
54
54
|
|
55
55
|
/**
|
56
56
|
* listEvents returns a list of all upcoming Events. If the current user is not logged in,
|
@@ -65,7 +65,7 @@ export type EventApiContextType = {
|
|
65
65
|
* @param event The Event to save.
|
66
66
|
* @returns An AxiosResponse containing the Event as saved in the database.
|
67
67
|
*/
|
68
|
-
setEvent: (event: Event) => Promise<AxiosResponse<Event
|
68
|
+
setEvent: (event: Event) => Promise<AxiosResponse<Event>>;
|
69
69
|
|
70
70
|
/**
|
71
71
|
* Adds the given message to the given event.
|
@@ -73,8 +73,8 @@ export type EventApiContextType = {
|
|
73
73
|
* @param content The text content of the message.
|
74
74
|
* @returns An AxiosResponse containing the updated Event.
|
75
75
|
*/
|
76
|
-
createMessage: (id: string, content: string) => Promise<AxiosResponse<Event
|
77
|
-
}
|
76
|
+
createMessage: (id: string, content: string) => Promise<AxiosResponse<Event>>;
|
77
|
+
}
|
78
78
|
|
79
79
|
export interface BookEventResponse {
|
80
80
|
event: Event;
|
@@ -177,7 +177,7 @@ interface ListEventsResponse {
|
|
177
177
|
* @returns A list of Events.
|
178
178
|
*/
|
179
179
|
export async function listEvents(idToken: string, startKey?: string) {
|
180
|
-
|
180
|
+
const params = { startKey };
|
181
181
|
const result: Event[] = [];
|
182
182
|
|
183
183
|
do {
|
@@ -4,7 +4,7 @@ import { Exam, ExamAnswer, ExamAttempt, ExamType } from '../database/exam';
|
|
4
4
|
|
5
5
|
const BASE_URL = getConfig().api.baseUrl;
|
6
6
|
|
7
|
-
export
|
7
|
+
export interface ExamApiContextType {
|
8
8
|
/**
|
9
9
|
* Fetches a list of exams with the provided type.
|
10
10
|
* @param type The type of exam to fetch.
|
@@ -33,7 +33,7 @@ export type ExamApiContextType = {
|
|
33
33
|
* @returns An AxiosResponse containing the requested ExamAnswer.
|
34
34
|
*/
|
35
35
|
getExamAnswer: (id: string) => Promise<AxiosResponse<ExamAnswer>>;
|
36
|
-
}
|
36
|
+
}
|
37
37
|
|
38
38
|
interface ListExamsResponse {
|
39
39
|
exams: Exam[];
|
@@ -12,13 +12,13 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
12
12
|
/**
|
13
13
|
* Provides an API for interacting with the position explorer.
|
14
14
|
*/
|
15
|
-
export
|
15
|
+
export interface ExplorerApiContextType {
|
16
16
|
/**
|
17
17
|
* Gets the ExplorerPosition with the provided FEN.
|
18
18
|
* @param fen The FEN to fetch.
|
19
19
|
* @returns The ExplorerPosition, if it exists.
|
20
20
|
*/
|
21
|
-
getPosition: (fen: string) => Promise<AxiosResponse<GetExplorerPositionResult
|
21
|
+
getPosition: (fen: string) => Promise<AxiosResponse<GetExplorerPositionResult>>;
|
22
22
|
|
23
23
|
/**
|
24
24
|
* Creates, updates or deletes an ExplorerPositionFollower with the provided parameters.
|
@@ -27,8 +27,8 @@ export type ExplorerApiContextType = {
|
|
27
27
|
*/
|
28
28
|
followPosition: (
|
29
29
|
request: FollowPositionRequest
|
30
|
-
) => Promise<AxiosResponse<ExplorerPositionFollower | null
|
31
|
-
}
|
30
|
+
) => Promise<AxiosResponse<ExplorerPositionFollower | null>>;
|
31
|
+
}
|
32
32
|
|
33
33
|
/** The result from a GetExplorerPosition request. */
|
34
34
|
export interface GetExplorerPositionResult {
|
@@ -5,7 +5,7 @@ import { Game, GameInfo, GameReviewType, PositionComment } from '../database/gam
|
|
5
5
|
|
6
6
|
const BASE_URL = getConfig().api.baseUrl;
|
7
7
|
|
8
|
-
export
|
8
|
+
export interface GameApiContextType {
|
9
9
|
/**
|
10
10
|
* createGame saves the provided game in the database.
|
11
11
|
* @param req The CreateGameRequest.
|
@@ -13,7 +13,7 @@ export type GameApiContextType = {
|
|
13
13
|
*/
|
14
14
|
createGame: (
|
15
15
|
req: CreateGameRequest,
|
16
|
-
) => Promise<AxiosResponse<Game | EditGameResponse
|
16
|
+
) => Promise<AxiosResponse<Game | EditGameResponse>>;
|
17
17
|
|
18
18
|
/**
|
19
19
|
* getGame returns the requested game.
|
@@ -21,7 +21,7 @@ export type GameApiContextType = {
|
|
21
21
|
* @param id The id of the game.
|
22
22
|
* @returns An AxiosResponse containing the requested game.
|
23
23
|
*/
|
24
|
-
getGame: (cohort: string, id: string) => Promise<AxiosResponse<Game
|
24
|
+
getGame: (cohort: string, id: string) => Promise<AxiosResponse<Game>>;
|
25
25
|
|
26
26
|
/**
|
27
27
|
* featureGame sets the featured status of the provided game.
|
@@ -34,7 +34,7 @@ export type GameApiContextType = {
|
|
34
34
|
cohort: string,
|
35
35
|
id: string,
|
36
36
|
featured: string,
|
37
|
-
) => Promise<AxiosResponse<Game
|
37
|
+
) => Promise<AxiosResponse<Game>>;
|
38
38
|
|
39
39
|
/**
|
40
40
|
* updateGame overwrites the PGN data of the provided game.
|
@@ -48,7 +48,7 @@ export type GameApiContextType = {
|
|
48
48
|
cohort: string,
|
49
49
|
id: string,
|
50
50
|
req: CreateGameRequest,
|
51
|
-
) => Promise<AxiosResponse<Game | EditGameResponse
|
51
|
+
) => Promise<AxiosResponse<Game | EditGameResponse>>;
|
52
52
|
|
53
53
|
/**
|
54
54
|
* deleteGame removes the specified game from the database. The caller
|
@@ -57,7 +57,7 @@ export type GameApiContextType = {
|
|
57
57
|
* @param id The id of the game.
|
58
58
|
* @returns The delete Game.
|
59
59
|
*/
|
60
|
-
deleteGame: (cohort: string, id: string) => Promise<AxiosResponse<Game
|
60
|
+
deleteGame: (cohort: string, id: string) => Promise<AxiosResponse<Game>>;
|
61
61
|
|
62
62
|
/**
|
63
63
|
* listGamesByCohort returns a list of GameInfo objects corresponding to the provided cohort,
|
@@ -73,7 +73,7 @@ export type GameApiContextType = {
|
|
73
73
|
startKey?: string,
|
74
74
|
startDate?: string,
|
75
75
|
endDate?: string,
|
76
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
76
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
77
77
|
|
78
78
|
/**
|
79
79
|
* listGamesByOwner returns a list of GameInfo objects owned by the provided user,
|
@@ -94,7 +94,7 @@ export type GameApiContextType = {
|
|
94
94
|
endDate?: string,
|
95
95
|
player?: string,
|
96
96
|
color?: string,
|
97
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
97
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
98
98
|
|
99
99
|
/**
|
100
100
|
* listGamesByOpening returns a list of GameInfo objects with the provided ECO code,
|
@@ -110,7 +110,7 @@ export type GameApiContextType = {
|
|
110
110
|
startKey?: string,
|
111
111
|
startDate?: string,
|
112
112
|
endDate?: string,
|
113
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
113
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
114
114
|
|
115
115
|
/**
|
116
116
|
* listGamesByPosition returns a list of GameInfo objects matching the provided FEN,
|
@@ -122,7 +122,7 @@ export type GameApiContextType = {
|
|
122
122
|
listGamesByPosition: (
|
123
123
|
fen: string,
|
124
124
|
startKey?: string,
|
125
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
125
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
126
126
|
|
127
127
|
/**
|
128
128
|
* listFeaturedGames returns a list of games featured in the past month.
|
@@ -138,7 +138,7 @@ export type GameApiContextType = {
|
|
138
138
|
*/
|
139
139
|
listGamesForReview: (
|
140
140
|
startKey?: string,
|
141
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
141
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
142
142
|
|
143
143
|
/**
|
144
144
|
* createComment adds the given content as a comment on the given game.
|
@@ -152,7 +152,7 @@ export type GameApiContextType = {
|
|
152
152
|
id: string,
|
153
153
|
comment: PositionComment,
|
154
154
|
existingComments: boolean,
|
155
|
-
) => Promise<AxiosResponse<Game
|
155
|
+
) => Promise<AxiosResponse<Game>>;
|
156
156
|
|
157
157
|
/**
|
158
158
|
* Updates a comment on a game. The full updated game is returned.
|
@@ -188,7 +188,7 @@ export type GameApiContextType = {
|
|
188
188
|
* @returns An AxiosResponse containing the updated game.
|
189
189
|
*/
|
190
190
|
markReviewed: (cohort: string, id: string) => Promise<AxiosResponse<Game>>;
|
191
|
-
}
|
191
|
+
}
|
192
192
|
|
193
193
|
export enum GameSubmissionType {
|
194
194
|
LichessChapter = 'lichessChapter',
|
@@ -341,7 +341,7 @@ export function listGamesByCohort(
|
|
341
341
|
startDate?: string,
|
342
342
|
endDate?: string,
|
343
343
|
) {
|
344
|
-
|
344
|
+
const params = { startDate, endDate, startKey };
|
345
345
|
cohort = encodeURIComponent(cohort);
|
346
346
|
return axios.get<ListGamesResponse>(BASE_URL + `/game/${cohort}`, {
|
347
347
|
params,
|
@@ -371,7 +371,7 @@ export function listGamesByOwner(
|
|
371
371
|
player?: string,
|
372
372
|
color?: string,
|
373
373
|
) {
|
374
|
-
|
374
|
+
const params = { owner, startKey, startDate, endDate, player, color };
|
375
375
|
return axios.get<ListGamesResponse>(BASE_URL + '/game', {
|
376
376
|
params,
|
377
377
|
headers: {
|
@@ -397,7 +397,7 @@ export function listGamesByOpening(
|
|
397
397
|
startDate?: string,
|
398
398
|
endDate?: string,
|
399
399
|
) {
|
400
|
-
|
400
|
+
const params = { eco, startKey, startDate, endDate };
|
401
401
|
return axios.get<ListGamesResponse>(BASE_URL + '/game/opening', {
|
402
402
|
params,
|
403
403
|
headers: {
|
@@ -415,7 +415,7 @@ export function listGamesByOpening(
|
|
415
415
|
* @returns A list of games matching the provided FEN.
|
416
416
|
*/
|
417
417
|
export function listGamesByPosition(idToken: string, fen: string, startKey?: string) {
|
418
|
-
|
418
|
+
const params = { fen, startKey };
|
419
419
|
return axios.get<ListGamesResponse>(BASE_URL + '/game/position', {
|
420
420
|
params,
|
421
421
|
headers: {
|
@@ -431,7 +431,7 @@ export function listGamesByPosition(idToken: string, fen: string, startKey?: str
|
|
431
431
|
* @returns A list of featured games.
|
432
432
|
*/
|
433
433
|
export async function listFeaturedGames(idToken: string, startKey?: string) {
|
434
|
-
|
434
|
+
const params = { startKey };
|
435
435
|
const result: GameInfo[] = [];
|
436
436
|
|
437
437
|
do {
|
@@ -520,7 +520,7 @@ export function updateComment(idToken: string, update: UpdateCommentRequest) {
|
|
520
520
|
});
|
521
521
|
}
|
522
522
|
|
523
|
-
export
|
523
|
+
export type DeleteCommentRequest = Omit<UpdateCommentRequest, 'content'>
|
524
524
|
|
525
525
|
/**
|
526
526
|
* Deletes a comment on a game. The full updated game is returned.
|
@@ -5,7 +5,7 @@ import { Graduation } from '../database/graduation';
|
|
5
5
|
|
6
6
|
const BASE_URL = getConfig().api.baseUrl;
|
7
7
|
|
8
|
-
export
|
8
|
+
export interface GraduationApiContextType {
|
9
9
|
/**
|
10
10
|
* listGraduationsByCohort returns a list of graduations matching the provided cohort.
|
11
11
|
* @param cohort The cohort to search for when matching graduations.
|
@@ -31,7 +31,7 @@ export type GraduationApiContextType = {
|
|
31
31
|
* @returns A list of graduations.
|
32
32
|
*/
|
33
33
|
listGraduationsByDate: (startKey?: string) => Promise<Graduation[]>;
|
34
|
-
}
|
34
|
+
}
|
35
35
|
|
36
36
|
interface ListGraduationsResponse {
|
37
37
|
graduations: Graduation[];
|
@@ -72,7 +72,7 @@ export async function listGraduationsByCohort(
|
|
72
72
|
cohort: string,
|
73
73
|
startKey?: string
|
74
74
|
) {
|
75
|
-
|
75
|
+
const params = { startKey };
|
76
76
|
return listGraduations(BASE_URL + `/graduations/${cohort}`, params, idToken);
|
77
77
|
}
|
78
78
|
|
@@ -88,7 +88,7 @@ export async function listGraduationsByOwner(
|
|
88
88
|
username: string,
|
89
89
|
startKey?: string
|
90
90
|
) {
|
91
|
-
|
91
|
+
const params = { startKey };
|
92
92
|
return listGraduations(BASE_URL + `/graduations/owner/${username}`, params, idToken);
|
93
93
|
}
|
94
94
|
|
@@ -99,6 +99,6 @@ export async function listGraduationsByOwner(
|
|
99
99
|
* @returns A list of graduations.
|
100
100
|
*/
|
101
101
|
export async function listGraduationsByDate(idToken: string, startKey?: string) {
|
102
|
-
|
102
|
+
const params = { startKey };
|
103
103
|
return listGraduations(BASE_URL + `/graduations`, params, idToken);
|
104
104
|
}
|
@@ -5,7 +5,7 @@ import { TimelineEntry } from '../database/timeline';
|
|
5
5
|
|
6
6
|
const BASE_URL = getConfig().api.baseUrl;
|
7
7
|
|
8
|
-
export
|
8
|
+
export interface NewsfeedApiContextType {
|
9
9
|
/**
|
10
10
|
* Fetches the timeline entry with the provided owner and id.
|
11
11
|
* @param owner The owner of the timeline entry.
|
@@ -15,7 +15,7 @@ export type NewsfeedApiContextType = {
|
|
15
15
|
getNewsfeedItem: (
|
16
16
|
owner: string,
|
17
17
|
id: string
|
18
|
-
) => Promise<AxiosResponse<TimelineEntry
|
18
|
+
) => Promise<AxiosResponse<TimelineEntry>>;
|
19
19
|
|
20
20
|
/**
|
21
21
|
* Fetches a page of the provided newsfeed.
|
@@ -28,7 +28,7 @@ export type NewsfeedApiContextType = {
|
|
28
28
|
newsfeedIds: string[],
|
29
29
|
skipLastFetch?: boolean,
|
30
30
|
startKey?: string
|
31
|
-
) => Promise<AxiosResponse<ListNewsfeedResponse
|
31
|
+
) => Promise<AxiosResponse<ListNewsfeedResponse>>;
|
32
32
|
|
33
33
|
/**
|
34
34
|
* Adds the given content as a comment on the given TimelineEntry.
|
@@ -39,7 +39,7 @@ export type NewsfeedApiContextType = {
|
|
39
39
|
createNewsfeedComment: (
|
40
40
|
props: { owner: string; id: string },
|
41
41
|
content: string
|
42
|
-
) => Promise<AxiosResponse<TimelineEntry
|
42
|
+
) => Promise<AxiosResponse<TimelineEntry>>;
|
43
43
|
|
44
44
|
/**
|
45
45
|
* Sets the provided reaction types on the given TimelineEntry.
|
@@ -52,8 +52,8 @@ export type NewsfeedApiContextType = {
|
|
52
52
|
owner: string,
|
53
53
|
id: string,
|
54
54
|
types: string[]
|
55
|
-
) => Promise<AxiosResponse<TimelineEntry
|
56
|
-
}
|
55
|
+
) => Promise<AxiosResponse<TimelineEntry>>;
|
56
|
+
}
|
57
57
|
|
58
58
|
/**
|
59
59
|
* Fetches the timeline entry with the provided owner and id.
|
@@ -8,7 +8,7 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
8
8
|
/**
|
9
9
|
* NotificationApiContextType provides an API for interacting with Notifications.
|
10
10
|
*/
|
11
|
-
export
|
11
|
+
export interface NotificationApiContextType {
|
12
12
|
/**
|
13
13
|
* listNotifications returns a list of Notifications for the current signed-in user.
|
14
14
|
* @param startKey The startKey to use when searching for Notifications.
|
@@ -16,15 +16,15 @@ export type NotificationApiContextType = {
|
|
16
16
|
*/
|
17
17
|
listNotifications: (
|
18
18
|
startKey?: string
|
19
|
-
) => Promise<AxiosResponse<ListNotificationsResponse
|
19
|
+
) => Promise<AxiosResponse<ListNotificationsResponse>>;
|
20
20
|
|
21
21
|
/**
|
22
22
|
* deleteNotification deletes the Notification with the provided id.
|
23
23
|
* @param id The id of the Notification to delete.
|
24
24
|
* @returns An empty AxiosResponse.
|
25
25
|
*/
|
26
|
-
deleteNotification: (id: string) => Promise<AxiosResponse<void
|
27
|
-
}
|
26
|
+
deleteNotification: (id: string) => Promise<AxiosResponse<void>>;
|
27
|
+
}
|
28
28
|
|
29
29
|
/**
|
30
30
|
* The response from a listNotifications request.
|
@@ -6,7 +6,7 @@ import { StripeAccount } from '../database/payment';
|
|
6
6
|
|
7
7
|
const BASE_URL = getConfig().api.baseUrl;
|
8
8
|
|
9
|
-
export
|
9
|
+
export interface PaymentApiContextType {
|
10
10
|
/**
|
11
11
|
* Creates a subscription checkout session.
|
12
12
|
* @param request The SubscriptionCheckoutRequest.
|
@@ -39,7 +39,7 @@ export type PaymentApiContextType = {
|
|
39
39
|
* @returns A stripe login link URL.
|
40
40
|
*/
|
41
41
|
paymentAccountLogin: () => Promise<AxiosResponse<StripeUrlResponse>>;
|
42
|
-
}
|
42
|
+
}
|
43
43
|
|
44
44
|
/** A request to create a subscription checkout session. */
|
45
45
|
export interface SubscriptionCheckoutRequest {
|
@@ -5,13 +5,13 @@ import { Requirement } from '../database/requirement';
|
|
5
5
|
|
6
6
|
const BASE_URL = getConfig().api.baseUrl;
|
7
7
|
|
8
|
-
export
|
8
|
+
export interface RequirementApiContextType {
|
9
9
|
/**
|
10
10
|
* getRequirement fetches the requirement with the provided id.
|
11
11
|
* @param id The id of the requirement to fetch.
|
12
12
|
* @returns The requirement with the provided id.
|
13
13
|
*/
|
14
|
-
getRequirement: (id: string) => Promise<AxiosResponse<Requirement
|
14
|
+
getRequirement: (id: string) => Promise<AxiosResponse<Requirement>>;
|
15
15
|
|
16
16
|
/**
|
17
17
|
* listRequirements returns a list of requirements matching the provided cohort.
|
@@ -32,8 +32,8 @@ export type RequirementApiContextType = {
|
|
32
32
|
*/
|
33
33
|
setRequirement: (
|
34
34
|
requirement: Requirement
|
35
|
-
) => Promise<AxiosResponse<Requirement
|
36
|
-
}
|
35
|
+
) => Promise<AxiosResponse<Requirement>>;
|
36
|
+
}
|
37
37
|
|
38
38
|
/**
|
39
39
|
* getRequirement fetches the requirement with the provided id.
|
@@ -68,7 +68,7 @@ export async function listRequirements(
|
|
68
68
|
scoreboardOnly: boolean,
|
69
69
|
startKey?: string
|
70
70
|
) {
|
71
|
-
|
71
|
+
const params = { scoreboardOnly, startKey };
|
72
72
|
const result: Requirement[] = [];
|
73
73
|
|
74
74
|
do {
|
@@ -9,17 +9,17 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
9
9
|
/**
|
10
10
|
* ScoreboardApiContextType provides an API for fetching the scoreboard.
|
11
11
|
*/
|
12
|
-
export
|
12
|
+
export interface ScoreboardApiContextType {
|
13
13
|
/**
|
14
14
|
* Returns the scoreboard data for the given scoreboard type.
|
15
15
|
* @param type The scoreboard type to get. Valid values are `dojo`, `following` or a cohort.
|
16
16
|
* @returns A list of User or ScoreboardSummary objects.
|
17
17
|
*/
|
18
|
-
getScoreboard: (type: string) => Promise<
|
19
|
-
}
|
18
|
+
getScoreboard: (type: string) => Promise<(User | ScoreboardSummary)[]>;
|
19
|
+
}
|
20
20
|
|
21
21
|
interface GetScoreboardResponse {
|
22
|
-
data:
|
22
|
+
data: (User | ScoreboardSummary)[];
|
23
23
|
lastEvaluatedKey: string;
|
24
24
|
}
|
25
25
|
|
@@ -32,9 +32,9 @@ interface GetScoreboardResponse {
|
|
32
32
|
export async function getScoreboard(
|
33
33
|
idToken: string,
|
34
34
|
type: string
|
35
|
-
): Promise<
|
36
|
-
|
37
|
-
const result:
|
35
|
+
): Promise<(User | ScoreboardSummary)[]> {
|
36
|
+
const params = { startKey: '' };
|
37
|
+
const result: (User | ScoreboardSummary)[] = [];
|
38
38
|
|
39
39
|
do {
|
40
40
|
const resp = await axios.get<GetScoreboardResponse>(
|
@@ -13,7 +13,7 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
13
13
|
export type TimePeriod = 'monthly' | 'yearly';
|
14
14
|
export type TimeControl = 'blitz' | 'rapid' | 'classical';
|
15
15
|
|
16
|
-
export
|
16
|
+
export interface TournamentApiContextType {
|
17
17
|
/**
|
18
18
|
* getLeaderboard returns the requested leaderboard.
|
19
19
|
* @param site The site the leaderboard is for.
|
@@ -37,7 +37,7 @@ export type TournamentApiContextType = {
|
|
37
37
|
* current tournament will be returned.
|
38
38
|
* @returns An AxiosResponse containing the requested OpenClassical.
|
39
39
|
*/
|
40
|
-
getOpenClassical: (startsAt?: string) => Promise<AxiosResponse<OpenClassical
|
40
|
+
getOpenClassical: (startsAt?: string) => Promise<AxiosResponse<OpenClassical>>;
|
41
41
|
|
42
42
|
/**
|
43
43
|
* Submits a request to register for the Open Classical.
|
@@ -46,7 +46,7 @@ export type TournamentApiContextType = {
|
|
46
46
|
*/
|
47
47
|
registerForOpenClassical: (
|
48
48
|
req: OpenClassicalRegistrationRequest,
|
49
|
-
) => Promise<AxiosResponse<void
|
49
|
+
) => Promise<AxiosResponse<void>>;
|
50
50
|
|
51
51
|
/**
|
52
52
|
* Submits a request to enter results for the Open Classical.
|
@@ -55,7 +55,7 @@ export type TournamentApiContextType = {
|
|
55
55
|
*/
|
56
56
|
submitResultsForOpenClassical: (
|
57
57
|
req: OpenClassicalSubmitResultsRequest,
|
58
|
-
) => Promise<AxiosResponse<OpenClassical
|
58
|
+
) => Promise<AxiosResponse<OpenClassical>>;
|
59
59
|
|
60
60
|
/**
|
61
61
|
* Sets the pairings using the given request. Only admins and tournament
|
@@ -65,7 +65,7 @@ export type TournamentApiContextType = {
|
|
65
65
|
*/
|
66
66
|
putOpenClassicalPairings: (
|
67
67
|
req: OpenClassicalPutPairingsRequest,
|
68
|
-
) => Promise<AxiosResponse<OpenClassical
|
68
|
+
) => Promise<AxiosResponse<OpenClassical>>;
|
69
69
|
|
70
70
|
/**
|
71
71
|
* Returns a list of previous open classicals.
|
@@ -83,7 +83,7 @@ export type TournamentApiContextType = {
|
|
83
83
|
adminGetRegistrations: (
|
84
84
|
region: string,
|
85
85
|
section: string,
|
86
|
-
) => Promise<AxiosResponse
|
86
|
+
) => Promise<AxiosResponse>;
|
87
87
|
|
88
88
|
/**
|
89
89
|
* Bans the given player from the open classical.
|
@@ -146,7 +146,7 @@ export type TournamentApiContextType = {
|
|
146
146
|
adminCompleteTournament: (
|
147
147
|
nextStartDate: string,
|
148
148
|
) => Promise<AxiosResponse<OpenClassical>>;
|
149
|
-
}
|
149
|
+
}
|
150
150
|
|
151
151
|
/** A request to register for the Open Classical. */
|
152
152
|
export interface OpenClassicalRegistrationRequest {
|
@@ -12,25 +12,25 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
12
12
|
/**
|
13
13
|
* UserApiContextType provides an API for interacting with the current signed-in user.
|
14
14
|
*/
|
15
|
-
export
|
15
|
+
export interface UserApiContextType {
|
16
16
|
/**
|
17
17
|
* checkUserAccess returns a 200 OK if the current signed-in user has an active subscription
|
18
18
|
* on chessdojo.shop and an error otherwise.
|
19
19
|
* @returns An empty AxiosResponse if the current user has an active subscription.
|
20
20
|
*/
|
21
|
-
checkUserAccess: () => Promise<AxiosResponse
|
21
|
+
checkUserAccess: () => Promise<AxiosResponse>;
|
22
22
|
|
23
23
|
/**
|
24
24
|
* getUser returns the current signed-in user.
|
25
25
|
* @returns An AxiosResponse containing the current user in the data field.
|
26
26
|
*/
|
27
|
-
getUser: () => Promise<AxiosResponse<User
|
27
|
+
getUser: () => Promise<AxiosResponse<User>>;
|
28
28
|
|
29
29
|
/**
|
30
30
|
* getUserPublic returns the user with the provided username.
|
31
31
|
* @returns An AxiosResponse containing the provided user in the data field.
|
32
32
|
*/
|
33
|
-
getUserPublic: (username: string) => Promise<AxiosResponse<User
|
33
|
+
getUserPublic: (username: string) => Promise<AxiosResponse<User>>;
|
34
34
|
|
35
35
|
/**
|
36
36
|
* listUserTimeline returns a list of the provided user's timeline entries.
|
@@ -69,7 +69,7 @@ export type UserApiContextType = {
|
|
69
69
|
updateUser: (
|
70
70
|
update: Partial<User>,
|
71
71
|
autopickCohort?: boolean,
|
72
|
-
) => Promise<AxiosResponse<User
|
72
|
+
) => Promise<AxiosResponse<User>>;
|
73
73
|
|
74
74
|
/**
|
75
75
|
* updateUserProgress updates the current user's progress on the provided requirement.
|
@@ -88,7 +88,7 @@ export type UserApiContextType = {
|
|
88
88
|
incrementalMinutesSpent: number,
|
89
89
|
date: DateTime | null,
|
90
90
|
notes: string,
|
91
|
-
) => Promise<AxiosResponse<User
|
91
|
+
) => Promise<AxiosResponse<User>>;
|
92
92
|
|
93
93
|
/**
|
94
94
|
* updateUserTimeline sets the current user's timeline for the provided requirement.
|
@@ -107,26 +107,26 @@ export type UserApiContextType = {
|
|
107
107
|
deleted: TimelineEntry[],
|
108
108
|
count: number,
|
109
109
|
minutesSpent: number,
|
110
|
-
) => Promise<AxiosResponse<User
|
110
|
+
) => Promise<AxiosResponse<User>>;
|
111
111
|
|
112
112
|
/**
|
113
113
|
* graduate creates a new graduation object for the given user and updates them to the next cohort.
|
114
114
|
* @param comments The comments the user wants to add to their graduation object.
|
115
115
|
* @returns An AxiosResponse containing the new graduation object and the user update.
|
116
116
|
*/
|
117
|
-
graduate: (comments: string) => Promise<AxiosResponse<GraduationResponse
|
117
|
+
graduate: (comments: string) => Promise<AxiosResponse<GraduationResponse>>;
|
118
118
|
|
119
119
|
/**
|
120
120
|
* @returns An AxiosResponse containing the user statistics.
|
121
121
|
*/
|
122
|
-
getUserStatistics: () => Promise<AxiosResponse<UserStatistics
|
122
|
+
getUserStatistics: () => Promise<AxiosResponse<UserStatistics>>;
|
123
123
|
|
124
124
|
/**
|
125
125
|
* Fetches the FollowerEntry for the current signed-in user and the given poster, if it exists.
|
126
126
|
* @param poster The person being followed.
|
127
127
|
* @returns The FollowerEntry or null if it does not exist.
|
128
128
|
*/
|
129
|
-
getFollower: (poster: string) => Promise<AxiosResponse<FollowerEntry | null
|
129
|
+
getFollower: (poster: string) => Promise<AxiosResponse<FollowerEntry | null>>;
|
130
130
|
|
131
131
|
/**
|
132
132
|
* Edits the follower state of the current signed-in user for the given poster.
|
@@ -137,7 +137,7 @@ export type UserApiContextType = {
|
|
137
137
|
editFollower: (
|
138
138
|
poster: string,
|
139
139
|
action: 'follow' | 'unfollow',
|
140
|
-
) => Promise<AxiosResponse<FollowerEntry | null
|
140
|
+
) => Promise<AxiosResponse<FollowerEntry | null>>;
|
141
141
|
|
142
142
|
/**
|
143
143
|
* Fetches a list of followers for the given user.
|
@@ -148,7 +148,7 @@ export type UserApiContextType = {
|
|
148
148
|
listFollowers: (
|
149
149
|
username: string,
|
150
150
|
startKey?: string,
|
151
|
-
) => Promise<AxiosResponse<ListFollowersResponse
|
151
|
+
) => Promise<AxiosResponse<ListFollowersResponse>>;
|
152
152
|
|
153
153
|
/**
|
154
154
|
* Fetches the list of users the given user is following.
|
@@ -159,8 +159,8 @@ export type UserApiContextType = {
|
|
159
159
|
listFollowing: (
|
160
160
|
username: string,
|
161
161
|
startKey?: string,
|
162
|
-
) => Promise<AxiosResponse<ListFollowersResponse
|
163
|
-
}
|
162
|
+
) => Promise<AxiosResponse<ListFollowersResponse>>;
|
163
|
+
}
|
164
164
|
|
165
165
|
/**
|
166
166
|
* checkUserAccess returns a 200 OK if the current signed-in user has an active subscription
|
@@ -215,7 +215,7 @@ export async function listUserTimeline(
|
|
215
215
|
owner: string,
|
216
216
|
startKey?: string,
|
217
217
|
) {
|
218
|
-
|
218
|
+
const params = { startKey };
|
219
219
|
const resp = await axios.get<ListUserTimelineResponse>(
|
220
220
|
`${BASE_URL}/user/${owner}/timeline`,
|
221
221
|
{
|
@@ -245,7 +245,7 @@ export async function listUsersByCohort(
|
|
245
245
|
cohort: string,
|
246
246
|
startKey?: string,
|
247
247
|
) {
|
248
|
-
|
248
|
+
const params = { startKey };
|
249
249
|
const result: User[] = [];
|
250
250
|
do {
|
251
251
|
const resp = await axios.get<ListUsersResponse>(BASE_URL + `/user/${cohort}`, {
|
@@ -268,7 +268,7 @@ export async function listUsersByCohort(
|
|
268
268
|
* @returns A list of users matching the provided query and fields.
|
269
269
|
*/
|
270
270
|
export async function searchUsers(query: string, fields: string[], startKey?: string) {
|
271
|
-
|
271
|
+
const params = { query, fields: fields.join(','), startKey };
|
272
272
|
const result: User[] = [];
|
273
273
|
|
274
274
|
do {
|
@@ -7,7 +7,7 @@ const BASE_URL = getConfig().api.baseUrl;
|
|
7
7
|
/**
|
8
8
|
* Provides an API for interacting with year reviews.
|
9
9
|
*/
|
10
|
-
export
|
10
|
+
export interface YearReviewApiContextType {
|
11
11
|
/**
|
12
12
|
* Fetches the year review for the provided user and year.
|
13
13
|
* @param username The username to fetch.
|
@@ -15,7 +15,7 @@ export type YearReviewApiContextType = {
|
|
15
15
|
* @returns The year review for the given user and year.
|
16
16
|
*/
|
17
17
|
getYearReview: (username: string, year: string) => Promise<AxiosResponse<YearReview>>;
|
18
|
-
}
|
18
|
+
}
|
19
19
|
|
20
20
|
/**
|
21
21
|
* Fetches the year review for the provided user and year.
|
@@ -110,7 +110,7 @@ const ForgotPasswordPage = () => {
|
|
110
110
|
{step === ForgotPasswordStep.Confirm && (
|
111
111
|
<ConfirmStep
|
112
112
|
email={email}
|
113
|
-
onSuccess={() => setStep(ForgotPasswordStep.Success)}
|
113
|
+
onSuccess={() => { setStep(ForgotPasswordStep.Success); }}
|
114
114
|
onCancel={onCancel}
|
115
115
|
/>
|
116
116
|
)}
|
@@ -163,7 +163,7 @@ const StartStep: React.FC<StartStepProps> = ({
|
|
163
163
|
label='Email'
|
164
164
|
variant='outlined'
|
165
165
|
value={email}
|
166
|
-
onChange={(event) => setEmail(event.target.value)}
|
166
|
+
onChange={(event) => { setEmail(event.target.value); }}
|
167
167
|
error={!!emailError}
|
168
168
|
helperText={emailError}
|
169
169
|
onKeyDown={onKeyDown}
|
@@ -274,7 +274,7 @@ const ConfirmStep: React.FC<ConfirmStepProps> = ({ email, onSuccess, onCancel })
|
|
274
274
|
label='Recovery Code'
|
275
275
|
variant='outlined'
|
276
276
|
value={code}
|
277
|
-
onChange={(event) => setCode(event.target.value)}
|
277
|
+
onChange={(event) => { setCode(event.target.value); }}
|
278
278
|
error={!!codeError}
|
279
279
|
helperText={codeError}
|
280
280
|
/>
|
@@ -286,7 +286,7 @@ const ConfirmStep: React.FC<ConfirmStepProps> = ({ email, onSuccess, onCancel })
|
|
286
286
|
type='password'
|
287
287
|
variant='outlined'
|
288
288
|
value={password}
|
289
|
-
onChange={(event) => setPassword(event.target.value)}
|
289
|
+
onChange={(event) => { setPassword(event.target.value); }}
|
290
290
|
error={!!passwordError}
|
291
291
|
helperText={passwordError}
|
292
292
|
/>
|
@@ -298,7 +298,7 @@ const ConfirmStep: React.FC<ConfirmStepProps> = ({ email, onSuccess, onCancel })
|
|
298
298
|
type='password'
|
299
299
|
variant='outlined'
|
300
300
|
value={passwordConfirm}
|
301
|
-
onChange={(event) => setPasswordConfirm(event.target.value)}
|
301
|
+
onChange={(event) => { setPasswordConfirm(event.target.value); }}
|
302
302
|
error={!!passwordError}
|
303
303
|
helperText={passwordError}
|
304
304
|
onKeyDown={onKeyDown}
|
@@ -99,7 +99,7 @@ const SigninPage = () => {
|
|
99
99
|
label='Email'
|
100
100
|
variant='outlined'
|
101
101
|
value={email}
|
102
|
-
onChange={(event) => setEmail(event.target.value)}
|
102
|
+
onChange={(event) => { setEmail(event.target.value); }}
|
103
103
|
error={!!errors.email}
|
104
104
|
helperText={errors.email}
|
105
105
|
/>
|
@@ -110,7 +110,7 @@ const SigninPage = () => {
|
|
110
110
|
type='password'
|
111
111
|
variant='outlined'
|
112
112
|
value={password}
|
113
|
-
onChange={(event) => setPassword(event.target.value)}
|
113
|
+
onChange={(event) => { setPassword(event.target.value); }}
|
114
114
|
onKeyDown={onKeyDown}
|
115
115
|
error={!!errors.password}
|
116
116
|
helperText={errors.password}
|
@@ -134,7 +134,7 @@ const SigninPage = () => {
|
|
134
134
|
data-cy='signup-button'
|
135
135
|
variant='text'
|
136
136
|
sx={{ textTransform: 'none' }}
|
137
|
-
onClick={() => navigate('/signup')}
|
137
|
+
onClick={() => { navigate('/signup'); }}
|
138
138
|
>
|
139
139
|
No account? Sign Up
|
140
140
|
</Button>
|
@@ -142,7 +142,7 @@ const SigninPage = () => {
|
|
142
142
|
data-cy='forgot-password-button'
|
143
143
|
variant='text'
|
144
144
|
sx={{ textTransform: 'none', alignSelf: 'end' }}
|
145
|
-
onClick={() => navigate('/forgot-password')}
|
145
|
+
onClick={() => { navigate('/forgot-password'); }}
|
146
146
|
>
|
147
147
|
Forgot password?
|
148
148
|
</Button>
|
@@ -108,7 +108,7 @@ const SignupPage = () => {
|
|
108
108
|
label='Name'
|
109
109
|
variant='outlined'
|
110
110
|
value={name}
|
111
|
-
onChange={(event) => setName(event.target.value)}
|
111
|
+
onChange={(event) => { setName(event.target.value); }}
|
112
112
|
error={!!errors.name}
|
113
113
|
helperText={errors.name}
|
114
114
|
/>
|
@@ -118,7 +118,7 @@ const SignupPage = () => {
|
|
118
118
|
label='Email'
|
119
119
|
variant='outlined'
|
120
120
|
value={email}
|
121
|
-
onChange={(event) => setEmail(event.target.value)}
|
121
|
+
onChange={(event) => { setEmail(event.target.value); }}
|
122
122
|
error={!!errors.email}
|
123
123
|
helperText={errors.email}
|
124
124
|
/>
|
@@ -129,7 +129,7 @@ const SignupPage = () => {
|
|
129
129
|
type='password'
|
130
130
|
variant='outlined'
|
131
131
|
value={password}
|
132
|
-
onChange={(event) => setPassword(event.target.value)}
|
132
|
+
onChange={(event) => { setPassword(event.target.value); }}
|
133
133
|
error={!!errors.password}
|
134
134
|
helperText={errors.password}
|
135
135
|
onKeyDown={onKeyDown}
|
@@ -10,7 +10,7 @@ const VerifyEmailPage = () => {
|
|
10
10
|
const auth = useAuth();
|
11
11
|
|
12
12
|
const navigate = useNavigate();
|
13
|
-
const locationState = useLocation().state
|
13
|
+
const locationState = useLocation().state;
|
14
14
|
|
15
15
|
const username: string = locationState?.username;
|
16
16
|
const email: string = locationState?.email;
|
@@ -125,7 +125,7 @@ const VerifyEmailPage = () => {
|
|
125
125
|
label='Verification Code'
|
126
126
|
variant='outlined'
|
127
127
|
value={code}
|
128
|
-
onChange={(event) => setCode(event.target.value)}
|
128
|
+
onChange={(event) => { setCode(event.target.value); }}
|
129
129
|
onKeyDown={onKeyDown}
|
130
130
|
error={!!codeError}
|
131
131
|
helperText={codeError}
|
@@ -74,7 +74,7 @@ export function toShapes(chess?: Chess): DrawShape[] {
|
|
74
74
|
}
|
75
75
|
|
76
76
|
const commentDiag = currentMove.commentDiag;
|
77
|
-
|
77
|
+
const result: DrawShape[] = [];
|
78
78
|
if (commentDiag) {
|
79
79
|
if (commentDiag.colorArrows) {
|
80
80
|
for (const comm of commentDiag.colorArrows) {
|
@@ -246,14 +246,14 @@ const Board: React.FC<BoardProps> = ({ config, onInitialize, onMove }) => {
|
|
246
246
|
dests: config?.movable?.dests || toDests(chess),
|
247
247
|
events: {
|
248
248
|
after: (orig, dest) =>
|
249
|
-
checkPromotion(
|
249
|
+
{ checkPromotion(
|
250
250
|
board,
|
251
251
|
chess,
|
252
252
|
orig,
|
253
253
|
dest,
|
254
254
|
onStartPromotion,
|
255
255
|
onMove ? onMove : defaultOnMove,
|
256
|
-
),
|
256
|
+
); },
|
257
257
|
},
|
258
258
|
},
|
259
259
|
lastMove: [],
|
@@ -296,14 +296,14 @@ const Board: React.FC<BoardProps> = ({ config, onInitialize, onMove }) => {
|
|
296
296
|
movable: {
|
297
297
|
events: {
|
298
298
|
after: (orig, dest) =>
|
299
|
-
checkPromotion(
|
299
|
+
{ checkPromotion(
|
300
300
|
board,
|
301
301
|
chess,
|
302
302
|
orig,
|
303
303
|
dest,
|
304
304
|
onStartPromotion,
|
305
305
|
onMove ? onMove : defaultOnMove,
|
306
|
-
),
|
306
|
+
); },
|
307
307
|
},
|
308
308
|
},
|
309
309
|
addPieceZIndex: pieceStyle === PieceStyle.ThreeD,
|
@@ -339,7 +339,7 @@ const Board: React.FC<BoardProps> = ({ config, onInitialize, onMove }) => {
|
|
339
339
|
<DialogContent>
|
340
340
|
<Stack direction='row'>
|
341
341
|
{promotionPieces.map((piece) => (
|
342
|
-
<Button key={piece} onClick={() => onFinishPromotion(piece)}>
|
342
|
+
<Button key={piece} onClick={() => { onFinishPromotion(piece); }}>
|
343
343
|
<Box
|
344
344
|
sx={{
|
345
345
|
width: '75px',
|
@@ -347,7 +347,7 @@ const Board: React.FC<BoardProps> = ({ config, onInitialize, onMove }) => {
|
|
347
347
|
backgroundSize: 'cover',
|
348
348
|
backgroundImage: `url(${
|
349
349
|
promotion
|
350
|
-
? `https://www.chess.com/chess-themes/pieces/bases/150/${promotion
|
350
|
+
? `https://www.chess.com/chess-themes/pieces/bases/150/${promotion.color[0]}${piece}.png`
|
351
351
|
: ''
|
352
352
|
})`,
|
353
353
|
}}
|
@@ -63,7 +63,7 @@ const KeyboardHandler: React.FC<KeyboardHandlerProps> = ({ underboardRef }) => {
|
|
63
63
|
event.preventDefault();
|
64
64
|
event.stopPropagation();
|
65
65
|
|
66
|
-
keyboardShortcutHandlers[matchedAction]
|
66
|
+
keyboardShortcutHandlers[matchedAction](chess, board, {
|
67
67
|
underboardApi: underboardRef.current,
|
68
68
|
toggleOrientation,
|
69
69
|
setVariationDialogMove:
|
@@ -207,7 +207,7 @@ export function getNagInSet(nagSet: Nag[], nags: string[] | undefined): Nag {
|
|
207
207
|
}
|
208
208
|
|
209
209
|
for (const nag of nags) {
|
210
|
-
|
210
|
+
const stdNag = getStandardNag(nag);
|
211
211
|
if (nagSet.includes(stdNag)) {
|
212
212
|
return stdNag;
|
213
213
|
}
|
@@ -251,8 +251,8 @@ export function setNagsInSet(newNags: Nag[], nagSet: Nag[], nags?: string[]): Na
|
|
251
251
|
}
|
252
252
|
|
253
253
|
export function compareNags(lhs: Nag, rhs: Nag): number {
|
254
|
-
|
255
|
-
|
254
|
+
const lhsNum = parseInt(lhs.slice(1));
|
255
|
+
const rhsNum = parseInt(rhs.slice(1));
|
256
256
|
|
257
257
|
if (lhsNum < rhsNum) {
|
258
258
|
return -1;
|
@@ -28,14 +28,14 @@ interface ChessConfig {
|
|
28
28
|
disableTakebacks?: Color | 'both';
|
29
29
|
}
|
30
30
|
|
31
|
-
|
31
|
+
interface ChessContextType {
|
32
32
|
chess?: Chess;
|
33
33
|
board?: BoardApi;
|
34
34
|
config?: ChessConfig;
|
35
35
|
toggleOrientation?: () => void;
|
36
36
|
keydownMap?: React.MutableRefObject<Record<string, boolean>>;
|
37
37
|
slots?: PgnBoardSlots;
|
38
|
-
}
|
38
|
+
}
|
39
39
|
|
40
40
|
export const ChessContext = createContext<ChessContextType>({});
|
41
41
|
|
@@ -130,7 +130,7 @@ const PgnBoard = forwardRef<PgnBoardApi, PgnBoardProps>(
|
|
130
130
|
|
131
131
|
const onClickMove = useCallback(
|
132
132
|
(move: Move | null) => {
|
133
|
-
chess
|
133
|
+
chess.seek(move);
|
134
134
|
reconcile(chess, board);
|
135
135
|
},
|
136
136
|
[chess, board],
|
@@ -26,7 +26,7 @@ export function getInitialClock(pgn?: Pgn): string | undefined {
|
|
26
26
|
|
27
27
|
const descriptor = timeControl.split(':')[0];
|
28
28
|
const time = descriptor.split('/').slice(-1)[0];
|
29
|
-
const startTime = parseInt(time
|
29
|
+
const startTime = parseInt(time.split('+')[0]);
|
30
30
|
if (isNaN(startTime) || startTime <= 0) {
|
31
31
|
return undefined;
|
32
32
|
}
|
@@ -41,7 +41,7 @@ export function getInitialClock(pgn?: Pgn): string | undefined {
|
|
41
41
|
result += `${minutes.toLocaleString(undefined, { minimumIntegerDigits: 2 })}:`;
|
42
42
|
|
43
43
|
const seconds = (startTime % 3600) % 60;
|
44
|
-
result +=
|
44
|
+
result += seconds.toLocaleString(undefined, { minimumIntegerDigits: 2 });
|
45
45
|
|
46
46
|
return result;
|
47
47
|
}
|
@@ -131,7 +131,7 @@ const PlayerHeader: React.FC<PlayerHeaderProps> = ({ type }) => {
|
|
131
131
|
};
|
132
132
|
|
133
133
|
chess.addObserver(observer);
|
134
|
-
return () => chess.removeObserver(observer);
|
134
|
+
return () => { chess.removeObserver(observer); };
|
135
135
|
}
|
136
136
|
}, [chess, setForceRender]);
|
137
137
|
|
@@ -142,13 +142,13 @@ const PlayerHeader: React.FC<PlayerHeaderProps> = ({ type }) => {
|
|
142
142
|
return <EmptyHeader type={type} light={light} />;
|
143
143
|
}
|
144
144
|
|
145
|
-
const currentMove = chess
|
145
|
+
const currentMove = chess.currentMove();
|
146
146
|
|
147
147
|
let playerName = '';
|
148
148
|
let playerElo = '';
|
149
149
|
let playerResult = '';
|
150
150
|
let move: Move | null | undefined = currentMove;
|
151
|
-
|
151
|
+
const clockCommand: 'emt' | 'clk' = move?.commentDiag?.emt ? 'emt' : 'clk';
|
152
152
|
let color: 'w' | 'b' = 'w';
|
153
153
|
|
154
154
|
if (
|
@@ -157,7 +157,7 @@ const PlayerHeader: React.FC<PlayerHeaderProps> = ({ type }) => {
|
|
157
157
|
) {
|
158
158
|
playerName = pgn.header.tags.Black;
|
159
159
|
playerElo = pgn.header.tags.BlackElo;
|
160
|
-
const resultTokens = pgn.header.tags.Result
|
160
|
+
const resultTokens = pgn.header.tags.Result.split('-');
|
161
161
|
if (resultTokens && resultTokens.length > 1) {
|
162
162
|
playerResult = resultTokens[1];
|
163
163
|
}
|
@@ -168,7 +168,7 @@ const PlayerHeader: React.FC<PlayerHeaderProps> = ({ type }) => {
|
|
168
168
|
} else {
|
169
169
|
playerName = pgn.header.tags.White;
|
170
170
|
playerElo = pgn.header.tags.WhiteElo;
|
171
|
-
const resultTokens = pgn.header.tags.Result
|
171
|
+
const resultTokens = pgn.header.tags.Result.split('-');
|
172
172
|
if (resultTokens && resultTokens.length > 1) {
|
173
173
|
playerResult = resultTokens[0];
|
174
174
|
}
|
@@ -302,7 +302,7 @@ const CapturedMaterial: React.FC<{ move: Move | null; color: 'w' | 'b' }> = ({
|
|
302
302
|
return null;
|
303
303
|
}
|
304
304
|
|
305
|
-
|
305
|
+
const materialDifference = move.materialDifference;
|
306
306
|
let displayedMaterialDiff = '';
|
307
307
|
if (color === 'w' && materialDifference > 0) {
|
308
308
|
displayedMaterialDiff = `+${materialDifference}`;
|
@@ -57,7 +57,7 @@ const VariationDialog: React.FC<VariationDialogProps> = ({ move, setMove }) => {
|
|
57
57
|
} else if (event.key === 'ArrowLeft' || event.key === 'Escape') {
|
58
58
|
setMove(null);
|
59
59
|
} else if (event.key >= '0' && event.key <= '9') {
|
60
|
-
|
60
|
+
const index = parseInt(event.key);
|
61
61
|
if (index === 0 && move.variations.length > 8) {
|
62
62
|
// 0 is out of order to match its position on the keyboard
|
63
63
|
selectMove(move.variations[8][0]);
|
@@ -69,7 +69,7 @@ const VariationDialog: React.FC<VariationDialogProps> = ({ move, setMove }) => {
|
|
69
69
|
}
|
70
70
|
};
|
71
71
|
window.addEventListener('keydown', onKeyDown);
|
72
|
-
return () => window.removeEventListener('keydown', onKeyDown);
|
72
|
+
return () => { window.removeEventListener('keydown', onKeyDown); };
|
73
73
|
}, [move, selected, setMove, selectMove]);
|
74
74
|
|
75
75
|
if (!move.variations || move.variations.length === 0) {
|
@@ -79,7 +79,7 @@ const VariationDialog: React.FC<VariationDialogProps> = ({ move, setMove }) => {
|
|
79
79
|
return (
|
80
80
|
<Dialog
|
81
81
|
open
|
82
|
-
onClose={() => setMove(null)}
|
82
|
+
onClose={() => { setMove(null); }}
|
83
83
|
classes={{
|
84
84
|
container: BlockBoardKeyboardShortcuts,
|
85
85
|
}}
|
@@ -94,13 +94,13 @@ const VariationDialog: React.FC<VariationDialogProps> = ({ move, setMove }) => {
|
|
94
94
|
<List>
|
95
95
|
<ListItemButton
|
96
96
|
selected={selected === 0}
|
97
|
-
onClick={() => selectMove(move)}
|
97
|
+
onClick={() => { selectMove(move); }}
|
98
98
|
>
|
99
99
|
<ListItemText sx={{ color: getTextColor(move) }}>
|
100
100
|
{move.san}
|
101
101
|
{move.nags
|
102
102
|
?.sort(compareNags)
|
103
|
-
.map((n) => nags[getStandardNag(n)]
|
103
|
+
.map((n) => nags[getStandardNag(n)].label || '')
|
104
104
|
.join('')}
|
105
105
|
</ListItemText>
|
106
106
|
<Typography variant='body2'>1</Typography>
|
@@ -115,13 +115,13 @@ const VariationDialog: React.FC<VariationDialogProps> = ({ move, setMove }) => {
|
|
115
115
|
<ListItemButton
|
116
116
|
key={variation[0].san}
|
117
117
|
selected={selected === i + 1}
|
118
|
-
onClick={() => selectMove(variation[0])}
|
118
|
+
onClick={() => { selectMove(variation[0]); }}
|
119
119
|
>
|
120
120
|
<ListItemText sx={{ color: getTextColor(variation[0]) }}>
|
121
121
|
{variation[0].san}
|
122
122
|
{variation[0].nags
|
123
123
|
?.sort(compareNags)
|
124
|
-
.map((n) => nags[getStandardNag(n)]
|
124
|
+
.map((n) => nags[getStandardNag(n)].label || '')
|
125
125
|
.join('')}
|
126
126
|
</ListItemText>
|
127
127
|
{i < 9 && (
|
@@ -58,7 +58,7 @@ const AnnotationWarnings = () => {
|
|
58
58
|
};
|
59
59
|
|
60
60
|
chess.addObserver(observer);
|
61
|
-
return () => chess.removeObserver(observer);
|
61
|
+
return () => { chess.removeObserver(observer); };
|
62
62
|
}
|
63
63
|
}, [chess, setForceRender]);
|
64
64
|
|
@@ -88,7 +88,7 @@ const AnnotationWarnings = () => {
|
|
88
88
|
<Button
|
89
89
|
size='small'
|
90
90
|
color='inherit'
|
91
|
-
onClick={() => setShowDetails(true)}
|
91
|
+
onClick={() => { setShowDetails(true); }}
|
92
92
|
>
|
93
93
|
Details
|
94
94
|
</Button>
|
@@ -100,7 +100,7 @@ const AnnotationWarnings = () => {
|
|
100
100
|
}.`}
|
101
101
|
</Alert>
|
102
102
|
|
103
|
-
<Dialog open={showDetails} onClose={() => setShowDetails(false)}>
|
103
|
+
<Dialog open={showDetails} onClose={() => { setShowDetails(false); }}>
|
104
104
|
<DialogTitle component='div'>
|
105
105
|
<Typography variant='h5'>Annotation Warnings</Typography>
|
106
106
|
</DialogTitle>
|
@@ -128,7 +128,7 @@ const AnnotationWarnings = () => {
|
|
128
128
|
width: 'fit-content',
|
129
129
|
ml: -1,
|
130
130
|
}}
|
131
|
-
onClick={() => onClickMove(m)}
|
131
|
+
onClick={() => { onClickMove(m); }}
|
132
132
|
>
|
133
133
|
{getMoveText(m)}
|
134
134
|
</Button>
|
@@ -10,7 +10,7 @@ export interface WarningRule {
|
|
10
10
|
export interface Warning {
|
11
11
|
displayName: string;
|
12
12
|
description: string;
|
13
|
-
moves:
|
13
|
+
moves: (Move | null)[];
|
14
14
|
}
|
15
15
|
|
16
16
|
const ChesscomCommentAfterRegex =
|
@@ -13,7 +13,7 @@ import { toDojoDateString, toDojoTimeString } from '../../../../calendar/display
|
|
13
13
|
import { Game } from '../../../../database/game';
|
14
14
|
import { useChess } from '../../PgnBoard';
|
15
15
|
|
16
|
-
const useDebounce = (callback: (...args: any) => void, delay
|
16
|
+
const useDebounce = (callback: (...args: any) => void, delay = 6000) => {
|
17
17
|
const ref = useRef<any>();
|
18
18
|
|
19
19
|
useEffect(() => {
|
@@ -97,7 +97,7 @@ const StatusIcon: React.FC<StatusIconProps> = ({ game }) => {
|
|
97
97
|
};
|
98
98
|
|
99
99
|
chess.addObserver(observer);
|
100
|
-
return () => chess.removeObserver(observer);
|
100
|
+
return () => { chess.removeObserver(observer); };
|
101
101
|
}
|
102
102
|
}, [chess, game, initialPgn, setInitialPgn, debouncedOnSave, setHasChanges]);
|
103
103
|
|
@@ -21,7 +21,7 @@ export function convertSecondsToDateTime(seconds: number | undefined): DateTime
|
|
21
21
|
}
|
22
22
|
|
23
23
|
function convertDateTimeToClock(date: DateTime | null): string {
|
24
|
-
if (!date
|
24
|
+
if (!date?.isValid) {
|
25
25
|
return '';
|
26
26
|
}
|
27
27
|
return formatTime(date.hour * 3600 + date.minute * 60 + date.second);
|
@@ -63,8 +63,8 @@ const ClockEditor = () => {
|
|
63
63
|
return null;
|
64
64
|
}
|
65
65
|
|
66
|
-
const initialClock = getInitialClock(chess
|
67
|
-
const increment = getIncrement(chess
|
66
|
+
const initialClock = getInitialClock(chess.pgn);
|
67
|
+
const increment = getIncrement(chess.pgn);
|
68
68
|
|
69
69
|
const moves = chess.history();
|
70
70
|
const grid = [];
|
@@ -99,7 +99,7 @@ const ClockEditor = () => {
|
|
99
99
|
label='Starting Time'
|
100
100
|
format='HH:mm:ss'
|
101
101
|
value={convertSecondsToDateTime(initialClock)}
|
102
|
-
onChange={(value) => handleInitialClock(chess, increment, value)}
|
102
|
+
onChange={(value) => { handleInitialClock(chess, increment, value); }}
|
103
103
|
fullWidth
|
104
104
|
/>
|
105
105
|
</Grid2>
|
@@ -109,7 +109,7 @@ const ClockEditor = () => {
|
|
109
109
|
id={BlockBoardKeyboardShortcuts}
|
110
110
|
label='Increment (Sec)'
|
111
111
|
value={`${increment}`}
|
112
|
-
onChange={(e) => handleIncrement(chess, initialClock, e.target.value)}
|
112
|
+
onChange={(e) => { handleIncrement(chess, initialClock, e.target.value); }}
|
113
113
|
fullWidth
|
114
114
|
/>
|
115
115
|
</Grid2>
|
@@ -45,7 +45,7 @@ const ClockTextField: React.FC<ClockTextFieldProps> = ({
|
|
45
45
|
convertClockToSeconds(move.commentDiag?.clk),
|
46
46
|
) || defaultDateTime
|
47
47
|
}
|
48
|
-
onChange={(value) => onChangeClock(chess, move, value)}
|
48
|
+
onChange={(value) => { onChangeClock(chess, move, value); }}
|
49
49
|
fullWidth
|
50
50
|
/>
|
51
51
|
);
|
@@ -62,13 +62,13 @@ const ClockTextField: React.FC<ClockTextFieldProps> = ({
|
|
62
62
|
value={timeSlots.hours}
|
63
63
|
disabled={!move}
|
64
64
|
onChange={(event) =>
|
65
|
-
onChangeTimeSlot(
|
65
|
+
{ onChangeTimeSlot(
|
66
66
|
'hours',
|
67
67
|
event.target.value,
|
68
68
|
timeSlots,
|
69
69
|
chess,
|
70
70
|
move,
|
71
|
-
)
|
71
|
+
); }
|
72
72
|
}
|
73
73
|
fullWidth
|
74
74
|
/>
|
@@ -78,13 +78,13 @@ const ClockTextField: React.FC<ClockTextFieldProps> = ({
|
|
78
78
|
value={timeSlots.minutes}
|
79
79
|
disabled={!move}
|
80
80
|
onChange={(event) =>
|
81
|
-
onChangeTimeSlot(
|
81
|
+
{ onChangeTimeSlot(
|
82
82
|
'minutes',
|
83
83
|
event.target.value,
|
84
84
|
timeSlots,
|
85
85
|
chess,
|
86
86
|
move,
|
87
|
-
)
|
87
|
+
); }
|
88
88
|
}
|
89
89
|
fullWidth
|
90
90
|
/>
|
@@ -94,13 +94,13 @@ const ClockTextField: React.FC<ClockTextFieldProps> = ({
|
|
94
94
|
value={timeSlots.seconds}
|
95
95
|
disabled={!move}
|
96
96
|
onChange={(event) =>
|
97
|
-
onChangeTimeSlot(
|
97
|
+
{ onChangeTimeSlot(
|
98
98
|
'seconds',
|
99
99
|
event.target.value,
|
100
100
|
timeSlots,
|
101
101
|
chess,
|
102
102
|
move,
|
103
|
-
)
|
103
|
+
); }
|
104
104
|
}
|
105
105
|
fullWidth
|
106
106
|
/>
|
@@ -126,9 +126,9 @@ function getTimeSlotsFromMove(move: Move): TimeSlots {
|
|
126
126
|
}
|
127
127
|
|
128
128
|
const slots = clock.split(':');
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
const seconds = parseFloat(slots[slots.length - 1] || '0');
|
130
|
+
const minutes = parseInt(slots[slots.length - 2] || '0');
|
131
|
+
const hours = parseInt(slots[slots.length - 3] || '0');
|
132
132
|
|
133
133
|
return {
|
134
134
|
hours,
|
@@ -160,7 +160,7 @@ function onChangeTimeSlot(
|
|
160
160
|
function getClockFromTimeSlots(slots: TimeSlots): string {
|
161
161
|
const seconds = slots.seconds % 60;
|
162
162
|
let minutes = slots.minutes + Math.floor(slots.seconds / 60);
|
163
|
-
|
163
|
+
const hours = slots.hours + Math.floor(minutes / 60);
|
164
164
|
minutes = minutes % 60;
|
165
165
|
|
166
166
|
return formatTime(hours * 3600 + minutes * 60 + seconds);
|
@@ -23,7 +23,7 @@ const primaryAxis: AxisOptions<Datum> = {
|
|
23
23
|
},
|
24
24
|
};
|
25
25
|
|
26
|
-
const secondaryAxes:
|
26
|
+
const secondaryAxes: AxisOptions<Datum>[] = [
|
27
27
|
{
|
28
28
|
getValue: (datum) => datum.seconds,
|
29
29
|
min: 0,
|
@@ -42,7 +42,7 @@ const barAxis: AxisOptions<Datum> = {
|
|
42
42
|
},
|
43
43
|
};
|
44
44
|
|
45
|
-
const secondaryBarAxis:
|
45
|
+
const secondaryBarAxis: AxisOptions<Datum>[] = [
|
46
46
|
{
|
47
47
|
...secondaryAxes[0],
|
48
48
|
position: 'bottom',
|
@@ -65,7 +65,7 @@ export function formatTime(value: number): string {
|
|
65
65
|
result += `${minutes.toLocaleString(undefined, { minimumIntegerDigits: 2 })}:`;
|
66
66
|
|
67
67
|
const seconds = (value % 3600) % 60;
|
68
|
-
result +=
|
68
|
+
result += seconds.toLocaleString(undefined, { minimumIntegerDigits: 2 });
|
69
69
|
return result;
|
70
70
|
}
|
71
71
|
|
@@ -81,7 +81,7 @@ export function getInitialClock(pgn?: Pgn): number {
|
|
81
81
|
|
82
82
|
const descriptor = timeControl.split(':')[0];
|
83
83
|
const time = descriptor.split('/').slice(-1)[0];
|
84
|
-
const startTime = parseInt(time
|
84
|
+
const startTime = parseInt(time.split('+')[0]);
|
85
85
|
if (isNaN(startTime) || startTime <= 0) {
|
86
86
|
return 0;
|
87
87
|
}
|
@@ -209,7 +209,7 @@ const ClockUsage: React.FC<ClockUsageProps> = ({ showEditor }) => {
|
|
209
209
|
};
|
210
210
|
|
211
211
|
chess.addObserver(observer);
|
212
|
-
return () => chess.removeObserver(observer);
|
212
|
+
return () => { chess.removeObserver(observer); };
|
213
213
|
}
|
214
214
|
}, [chess, setForceRender, showEditor]);
|
215
215
|
|
@@ -94,7 +94,7 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
94
94
|
};
|
95
95
|
|
96
96
|
chess.addObserver(observer);
|
97
|
-
return () => chess.removeObserver(observer);
|
97
|
+
return () => { chess.removeObserver(observer); };
|
98
98
|
}
|
99
99
|
}, [chess, setForceRender]);
|
100
100
|
|
@@ -152,7 +152,7 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
152
152
|
format='HH:mm:ss'
|
153
153
|
value={convertSecondsToDateTime(initialClock)}
|
154
154
|
onChange={(value) =>
|
155
|
-
handleInitialClock(chess, increment, value)
|
155
|
+
{ handleInitialClock(chess, increment, value); }
|
156
156
|
}
|
157
157
|
fullWidth
|
158
158
|
/>
|
@@ -164,11 +164,11 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
164
164
|
label='Increment (Sec)'
|
165
165
|
value={`${increment}`}
|
166
166
|
onChange={(e) =>
|
167
|
-
handleIncrement(
|
167
|
+
{ handleIncrement(
|
168
168
|
chess,
|
169
169
|
initialClock,
|
170
170
|
e.target.value,
|
171
|
-
)
|
171
|
+
); }
|
172
172
|
}
|
173
173
|
fullWidth
|
174
174
|
/>
|
@@ -182,10 +182,10 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
182
182
|
label='Comments'
|
183
183
|
id={BlockBoardKeyboardShortcuts}
|
184
184
|
multiline
|
185
|
-
minRows={
|
186
|
-
maxRows={
|
185
|
+
minRows={move ? (isMainline ? 3 : 7) : 15}
|
186
|
+
maxRows={move ? 9 : 15}
|
187
187
|
value={comment}
|
188
|
-
onChange={(event) => chess
|
188
|
+
onChange={(event) => { chess.setComment(event.target.value); }}
|
189
189
|
fullWidth
|
190
190
|
/>
|
191
191
|
|
@@ -194,7 +194,7 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
194
194
|
<Stack spacing={1}>
|
195
195
|
<ToggleButtonGroup
|
196
196
|
exclusive
|
197
|
-
value={getNagInSet(moveNags, chess
|
197
|
+
value={getNagInSet(moveNags, chess.currentMove()?.nags)}
|
198
198
|
onChange={handleExclusiveNag(moveNags)}
|
199
199
|
>
|
200
200
|
{moveNags.map((nag) => (
|
@@ -209,7 +209,7 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
209
209
|
|
210
210
|
<ToggleButtonGroup
|
211
211
|
exclusive
|
212
|
-
value={getNagInSet(evalNags, chess
|
212
|
+
value={getNagInSet(evalNags, chess.currentMove()?.nags)}
|
213
213
|
onChange={handleExclusiveNag(evalNags)}
|
214
214
|
>
|
215
215
|
{evalNags.map((nag) => (
|
@@ -225,7 +225,7 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
225
225
|
<ToggleButtonGroup
|
226
226
|
value={getNagsInSet(
|
227
227
|
positionalNags,
|
228
|
-
chess
|
228
|
+
chess.currentMove()?.nags,
|
229
229
|
)}
|
230
230
|
onChange={handleMultiNags(positionalNags)}
|
231
231
|
>
|
@@ -244,8 +244,8 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
244
244
|
<Button
|
245
245
|
startIcon={<CheckIcon />}
|
246
246
|
variant='outlined'
|
247
|
-
disabled={chess
|
248
|
-
onClick={() => chess
|
247
|
+
disabled={chess.isInMainline(move) || takebacksDisabled}
|
248
|
+
onClick={() => { chess.promoteVariation(move, true); }}
|
249
249
|
>
|
250
250
|
Make main line
|
251
251
|
</Button>
|
@@ -253,16 +253,16 @@ const Editor: React.FC<EditorProps> = ({ focusEditor, setFocusEditor }) => {
|
|
253
253
|
startIcon={<ArrowUpwardIcon />}
|
254
254
|
variant='outlined'
|
255
255
|
disabled={
|
256
|
-
!chess
|
256
|
+
!chess.canPromoteVariation(move) || takebacksDisabled
|
257
257
|
}
|
258
|
-
onClick={() => chess
|
258
|
+
onClick={() => { chess.promoteVariation(move); }}
|
259
259
|
>
|
260
260
|
Move variation up
|
261
261
|
</Button>
|
262
262
|
<Button
|
263
263
|
startIcon={<DeleteIcon />}
|
264
264
|
variant='outlined'
|
265
|
-
onClick={() => chess
|
265
|
+
onClick={() => { chess.delete(move); }}
|
266
266
|
disabled={!config?.allowMoveDeletion || takebacksDisabled}
|
267
267
|
>
|
268
268
|
Delete from here
|
@@ -135,7 +135,7 @@ const Tags: React.FC<TagsProps> = ({ game, allowEdits }) => {
|
|
135
135
|
};
|
136
136
|
|
137
137
|
chess.addObserver(observer);
|
138
|
-
return () => chess.removeObserver(observer);
|
138
|
+
return () => { chess.removeObserver(observer); };
|
139
139
|
}
|
140
140
|
}, [chess]);
|
141
141
|
|
@@ -159,7 +159,7 @@ const Tags: React.FC<TagsProps> = ({ game, allowEdits }) => {
|
|
159
159
|
rows.push({ name: 'Cohort', value: game.cohort });
|
160
160
|
}
|
161
161
|
|
162
|
-
rows.push(...defaultTags.map((name) => ({ name, value: tags
|
162
|
+
rows.push(...defaultTags.map((name) => ({ name, value: tags[name] || '' })));
|
163
163
|
|
164
164
|
for (const [tag, value] of Object.entries(tags || {})) {
|
165
165
|
if (!defaultTags.includes(tag) && !uneditableTags.includes(tag)) {
|
@@ -168,7 +168,7 @@ const Tags: React.FC<TagsProps> = ({ game, allowEdits }) => {
|
|
168
168
|
}
|
169
169
|
|
170
170
|
for (const tag of uneditableTags) {
|
171
|
-
rows.push({ name: tag, value: tags
|
171
|
+
rows.push({ name: tag, value: tags[tag] || '' });
|
172
172
|
}
|
173
173
|
|
174
174
|
return (
|
@@ -117,7 +117,7 @@ const Underboard = forwardRef<UnderboardApi, UnderboardProps>(
|
|
117
117
|
? initialTab
|
118
118
|
: isOwner
|
119
119
|
? DefaultUnderboardTab.Editor
|
120
|
-
:
|
120
|
+
: game
|
121
121
|
? DefaultUnderboardTab.Comments
|
122
122
|
: DefaultUnderboardTab.Explorer,
|
123
123
|
);
|
@@ -77,7 +77,7 @@ const BaseComment: React.FC<BaseCommentProps> = ({
|
|
77
77
|
<Stack direction='row' spacing={0.5}>
|
78
78
|
{!expanded && (
|
79
79
|
<Tooltip title='Expand Comment'>
|
80
|
-
<IconButton onClick={() => setExpanded(true)} size='small'>
|
80
|
+
<IconButton onClick={() => { setExpanded(true); }} size='small'>
|
81
81
|
<ExpandMore
|
82
82
|
fontSize='small'
|
83
83
|
sx={{ color: 'text.secondary' }}
|
@@ -102,7 +102,7 @@ const BaseComment: React.FC<BaseCommentProps> = ({
|
|
102
102
|
borderColor: 'primary.main',
|
103
103
|
},
|
104
104
|
}}
|
105
|
-
onClick={() => setExpanded(false)}
|
105
|
+
onClick={() => { setExpanded(false); }}
|
106
106
|
>
|
107
107
|
<Divider orientation='vertical' />
|
108
108
|
</Stack>
|
@@ -122,7 +122,7 @@ const BaseComment: React.FC<BaseCommentProps> = ({
|
|
122
122
|
{isReplying ? (
|
123
123
|
<ReplyEditor
|
124
124
|
parent={comment}
|
125
|
-
onCancel={() => setIsReplying(false)}
|
125
|
+
onCancel={() => { setIsReplying(false); }}
|
126
126
|
/>
|
127
127
|
) : (
|
128
128
|
!isReadonly &&
|
@@ -131,7 +131,7 @@ const BaseComment: React.FC<BaseCommentProps> = ({
|
|
131
131
|
<Button
|
132
132
|
size='small'
|
133
133
|
sx={{ textTransform: 'none', minWidth: 0 }}
|
134
|
-
onClick={() => setIsReplying(true)}
|
134
|
+
onClick={() => { setIsReplying(true); }}
|
135
135
|
>
|
136
136
|
reply
|
137
137
|
</Button>
|
@@ -225,7 +225,7 @@ const EditableComment: React.FC<CommentProps> = ({ comment }) => {
|
|
225
225
|
<TextField
|
226
226
|
id={BlockBoardKeyboardShortcuts}
|
227
227
|
value={editValue}
|
228
|
-
onChange={(e) => setEditValue(e.target.value)}
|
228
|
+
onChange={(e) => { setEditValue(e.target.value); }}
|
229
229
|
onKeyDown={onKeyDown}
|
230
230
|
size='small'
|
231
231
|
sx={{ pt: 0.5 }}
|
@@ -238,7 +238,7 @@ const EditableComment: React.FC<CommentProps> = ({ comment }) => {
|
|
238
238
|
<Button
|
239
239
|
size='small'
|
240
240
|
sx={{ textTransform: 'none' }}
|
241
|
-
onClick={() => setEditValue(undefined)}
|
241
|
+
onClick={() => { setEditValue(undefined); }}
|
242
242
|
disabled={request.isLoading()}
|
243
243
|
>
|
244
244
|
cancel
|
@@ -262,14 +262,14 @@ const EditableComment: React.FC<CommentProps> = ({ comment }) => {
|
|
262
262
|
<Button
|
263
263
|
size='small'
|
264
264
|
sx={{ textTransform: 'none', minWidth: 0 }}
|
265
|
-
onClick={() => setEditValue(comment.content)}
|
265
|
+
onClick={() => { setEditValue(comment.content); }}
|
266
266
|
>
|
267
267
|
edit
|
268
268
|
</Button>
|
269
269
|
<Button
|
270
270
|
size='small'
|
271
271
|
sx={{ textTransform: 'none', minWidth: 0 }}
|
272
|
-
onClick={() => setShowDelete(true)}
|
272
|
+
onClick={() => { setShowDelete(true); }}
|
273
273
|
>
|
274
274
|
delete
|
275
275
|
</Button>
|
@@ -282,7 +282,7 @@ const EditableComment: React.FC<CommentProps> = ({ comment }) => {
|
|
282
282
|
<Dialog
|
283
283
|
open={showDelete}
|
284
284
|
onClose={
|
285
|
-
deleteRequest.isLoading() ? undefined : () => setShowDelete(false)
|
285
|
+
deleteRequest.isLoading() ? undefined : () => { setShowDelete(false); }
|
286
286
|
}
|
287
287
|
>
|
288
288
|
<DialogTitle>Delete Comment?</DialogTitle>
|
@@ -294,7 +294,7 @@ const EditableComment: React.FC<CommentProps> = ({ comment }) => {
|
|
294
294
|
<DialogActions>
|
295
295
|
<Button
|
296
296
|
disabled={deleteRequest.isLoading()}
|
297
|
-
onClick={() => setShowDelete(false)}
|
297
|
+
onClick={() => { setShowDelete(false); }}
|
298
298
|
>
|
299
299
|
Cancel
|
300
300
|
</Button>
|
@@ -58,7 +58,7 @@ const CommentEditor: React.FC<CommentEditorProps> = ({ focusEditor, setFocusEdit
|
|
58
58
|
parentIds: '',
|
59
59
|
replies: {},
|
60
60
|
};
|
61
|
-
const existingComments = Boolean(game.positionComments
|
61
|
+
const existingComments = Boolean(game.positionComments[positionComment.fen]);
|
62
62
|
|
63
63
|
request.onStart();
|
64
64
|
api.createComment(game.cohort, game.id, positionComment, existingComments)
|
@@ -100,7 +100,7 @@ const CommentEditor: React.FC<CommentEditorProps> = ({ focusEditor, setFocusEdit
|
|
100
100
|
fullWidth
|
101
101
|
multiline
|
102
102
|
value={comment}
|
103
|
-
onChange={(e) => setComment(e.target.value)}
|
103
|
+
onChange={(e) => { setComment(e.target.value); }}
|
104
104
|
onKeyDown={onKeyDown}
|
105
105
|
disabled={request.isLoading()}
|
106
106
|
maxRows={8}
|
@@ -30,9 +30,9 @@ export enum SortBy {
|
|
30
30
|
Oldest = 'OLDEST',
|
31
31
|
}
|
32
32
|
|
33
|
-
|
33
|
+
interface PositionCommentSortContextType {
|
34
34
|
sortBy: SortBy;
|
35
|
-
}
|
35
|
+
}
|
36
36
|
|
37
37
|
const PositionCommentSortContext = createContext<PositionCommentSortContextType>({
|
38
38
|
sortBy: SortBy.Newest,
|
@@ -76,7 +76,7 @@ const Comments: React.FC<CommentsProps> = ({
|
|
76
76
|
},
|
77
77
|
};
|
78
78
|
chess.addObserver(observer);
|
79
|
-
return () => chess.removeObserver(observer);
|
79
|
+
return () => { chess.removeObserver(observer); };
|
80
80
|
}
|
81
81
|
}, [chess, setForceRender]);
|
82
82
|
|
@@ -95,7 +95,7 @@ const Comments: React.FC<CommentsProps> = ({
|
|
95
95
|
label='Show Comments From'
|
96
96
|
select
|
97
97
|
value={view}
|
98
|
-
onChange={(e) => setView(e.target.value as View)}
|
98
|
+
onChange={(e) => { setView(e.target.value as View); }}
|
99
99
|
fullWidth
|
100
100
|
size='small'
|
101
101
|
>
|
@@ -109,7 +109,7 @@ const Comments: React.FC<CommentsProps> = ({
|
|
109
109
|
label='Sort By'
|
110
110
|
select
|
111
111
|
value={sortBy}
|
112
|
-
onChange={(e) => setSortBy(e.target.value as SortBy)}
|
112
|
+
onChange={(e) => { setSortBy(e.target.value as SortBy); }}
|
113
113
|
fullWidth
|
114
114
|
size='small'
|
115
115
|
>
|
@@ -79,7 +79,7 @@ const ReplyEditor: React.FC<ReplyEditorProps> = ({ parent, onCancel }) => {
|
|
79
79
|
size='small'
|
80
80
|
placeholder={`Reply to ${parent.owner.displayName} (${parent.owner.cohort})`}
|
81
81
|
value={value}
|
82
|
-
onChange={(e) => setValue(e.target.value)}
|
82
|
+
onChange={(e) => { setValue(e.target.value); }}
|
83
83
|
onKeyDown={onKeyDown}
|
84
84
|
disabled={request.isLoading()}
|
85
85
|
multiline
|
@@ -21,7 +21,7 @@ const EditorSettings = () => {
|
|
21
21
|
select
|
22
22
|
label='Clock Field Format'
|
23
23
|
value={clockFieldFormat}
|
24
|
-
onChange={(e) => setClockFieldFormat(e.target.value)}
|
24
|
+
onChange={(e) => { setClockFieldFormat(e.target.value); }}
|
25
25
|
>
|
26
26
|
<MenuItem value={ClockFieldFormat.SingleField}>Single Field</MenuItem>
|
27
27
|
<MenuItem value={ClockFieldFormat.ThreeField}>Three Fields</MenuItem>
|
@@ -77,7 +77,7 @@ const GameSettings: React.FC<GameSettingsProps> = ({ game, onSaveGame }) => {
|
|
77
77
|
<RadioGroup
|
78
78
|
row
|
79
79
|
value={visibility}
|
80
|
-
onChange={(e) => setVisibility(e.target.value)}
|
80
|
+
onChange={(e) => { setVisibility(e.target.value); }}
|
81
81
|
>
|
82
82
|
<FormControlLabel
|
83
83
|
value='public'
|
@@ -102,7 +102,7 @@ const GameSettings: React.FC<GameSettingsProps> = ({ game, onSaveGame }) => {
|
|
102
102
|
<RadioGroup
|
103
103
|
row
|
104
104
|
value={orientation}
|
105
|
-
onChange={(e) => setOrientation(e.target.value)}
|
105
|
+
onChange={(e) => { setOrientation(e.target.value); }}
|
106
106
|
>
|
107
107
|
<FormControlLabel
|
108
108
|
value='white'
|
@@ -130,7 +130,7 @@ const GameSettings: React.FC<GameSettingsProps> = ({ game, onSaveGame }) => {
|
|
130
130
|
|
131
131
|
<RequestReviewDialog game={game} />
|
132
132
|
|
133
|
-
<Button variant='outlined' onClick={() => navigate('edit')}>
|
133
|
+
<Button variant='outlined' onClick={() => { navigate('edit'); }}>
|
134
134
|
Replace PGN
|
135
135
|
</Button>
|
136
136
|
<DeleteGameButton variant='contained' game={game} />
|
@@ -686,7 +686,7 @@ const KeyboardShortcuts = () => {
|
|
686
686
|
fullWidth
|
687
687
|
select
|
688
688
|
value={binding.modifier}
|
689
|
-
onChange={(e) => onChangeModifier(a, e.target.value)}
|
689
|
+
onChange={(e) => { onChangeModifier(a, e.target.value); }}
|
690
690
|
SelectProps={{
|
691
691
|
displayEmpty: true,
|
692
692
|
}}
|
@@ -709,7 +709,7 @@ const KeyboardShortcuts = () => {
|
|
709
709
|
width: 1,
|
710
710
|
height: '36.5px',
|
711
711
|
}}
|
712
|
-
onClick={() => onOpenEditor(a)}
|
712
|
+
onClick={() => { onOpenEditor(a); }}
|
713
713
|
>
|
714
714
|
{displayKey(binding.key)}
|
715
715
|
</Button>
|
@@ -43,11 +43,11 @@ interface RequestReviewDialogProps {
|
|
43
43
|
|
44
44
|
const RequestReviewDialog: React.FC<RequestReviewDialogProps> = ({ game }) => {
|
45
45
|
const [open, setOpen] = useState(false);
|
46
|
-
const onClose = () => setOpen(false);
|
46
|
+
const onClose = () => { setOpen(false); };
|
47
47
|
|
48
48
|
return (
|
49
49
|
<>
|
50
|
-
<Button variant='contained' onClick={() => setOpen(true)}>
|
50
|
+
<Button variant='contained' onClick={() => { setOpen(true); }}>
|
51
51
|
{!game.review
|
52
52
|
? 'Request Sensei Review'
|
53
53
|
: game.review.reviewedAt
|
@@ -175,7 +175,7 @@ const SubmitDialogContent: React.FC<{
|
|
175
175
|
<FormLabel>Review Type</FormLabel>
|
176
176
|
<RadioGroup
|
177
177
|
value={reviewType}
|
178
|
-
onChange={(e) => setReviewType(e.target.value as GameReviewType)}
|
178
|
+
onChange={(e) => { setReviewType(e.target.value as GameReviewType); }}
|
179
179
|
>
|
180
180
|
<FormControlLabel
|
181
181
|
value={GameReviewType.Quick}
|
@@ -197,7 +197,7 @@ const SubmitDialogContent: React.FC<{
|
|
197
197
|
control={
|
198
198
|
<Checkbox
|
199
199
|
checked={isConfirmed}
|
200
|
-
onChange={(e) => setIsConfirmed(e.target.checked)}
|
200
|
+
onChange={(e) => { setIsConfirmed(e.target.checked); }}
|
201
201
|
sx={{
|
202
202
|
color:
|
203
203
|
errors.isConfirmed && !isConfirmed
|
@@ -102,7 +102,7 @@ const ViewerSettings = () => {
|
|
102
102
|
select
|
103
103
|
label='Board Style'
|
104
104
|
value={boardStyle}
|
105
|
-
onChange={(e) => setBoardStyle(e.target.value)}
|
105
|
+
onChange={(e) => { setBoardStyle(e.target.value); }}
|
106
106
|
>
|
107
107
|
<MenuItem value={BoardStyle.Standard}>Standard</MenuItem>
|
108
108
|
<MenuItem value={BoardStyle.CherryBlossom}>Cherry Blossom</MenuItem>
|
@@ -117,7 +117,7 @@ const ViewerSettings = () => {
|
|
117
117
|
select
|
118
118
|
label='Piece Style'
|
119
119
|
value={pieceStyle}
|
120
|
-
onChange={(e) => setPieceStyle(e.target.value)}
|
120
|
+
onChange={(e) => { setPieceStyle(e.target.value); }}
|
121
121
|
>
|
122
122
|
<MenuItem value={PieceStyle.Standard}>Standard</MenuItem>
|
123
123
|
<MenuItem value={PieceStyle.Cburnett}>Cburnett</MenuItem>
|
@@ -133,7 +133,7 @@ const ViewerSettings = () => {
|
|
133
133
|
select
|
134
134
|
label='Coordinate Style'
|
135
135
|
value={coordinateStyle}
|
136
|
-
onChange={(e) => setCoordinateStyle(e.target.value as CoordinateStyle)}
|
136
|
+
onChange={(e) => { setCoordinateStyle(e.target.value as CoordinateStyle); }}
|
137
137
|
>
|
138
138
|
<MenuItem value={CoordinateStyle.None}>None</MenuItem>
|
139
139
|
<MenuItem value={CoordinateStyle.RankFileOnly}>
|
@@ -146,7 +146,7 @@ const ViewerSettings = () => {
|
|
146
146
|
select
|
147
147
|
label='Go to Start/End Button Behavior'
|
148
148
|
value={goToEndBehavior}
|
149
|
-
onChange={(e) => setGoToEndBehavior(e.target.value)}
|
149
|
+
onChange={(e) => { setGoToEndBehavior(e.target.value); }}
|
150
150
|
>
|
151
151
|
<MenuItem value={GoToEndButtonBehavior.SingleClick}>
|
152
152
|
Single Click
|
@@ -161,7 +161,7 @@ const ViewerSettings = () => {
|
|
161
161
|
select
|
162
162
|
label='Variation Behavior'
|
163
163
|
value={variationBehavior}
|
164
|
-
onChange={(e) => setVariationBehavior(e.target.value)}
|
164
|
+
onChange={(e) => { setVariationBehavior(e.target.value); }}
|
165
165
|
>
|
166
166
|
<MenuItem value={VariationBehavior.None}>None</MenuItem>
|
167
167
|
<MenuItem value={VariationBehavior.Dialog}>Prompt in Dialog</MenuItem>
|
@@ -171,7 +171,7 @@ const ViewerSettings = () => {
|
|
171
171
|
select
|
172
172
|
label='Captured Material Display'
|
173
173
|
value={capturedMaterialBehavior}
|
174
|
-
onChange={(e) => setCapturedMaterialBehavior(e.target.value)}
|
174
|
+
onChange={(e) => { setCapturedMaterialBehavior(e.target.value); }}
|
175
175
|
>
|
176
176
|
<MenuItem value={CapturedMaterialBehavior.None}>None</MenuItem>
|
177
177
|
<MenuItem value={CapturedMaterialBehavior.Difference}>
|
@@ -187,7 +187,7 @@ const ViewerSettings = () => {
|
|
187
187
|
control={
|
188
188
|
<Checkbox
|
189
189
|
checked={showLegalMoves}
|
190
|
-
onChange={(e) => setShowLegalMoves(e.target.checked)}
|
190
|
+
onChange={(e) => { setShowLegalMoves(e.target.checked); }}
|
191
191
|
/>
|
192
192
|
}
|
193
193
|
label='Show legal moves'
|
@@ -197,7 +197,7 @@ const ViewerSettings = () => {
|
|
197
197
|
control={
|
198
198
|
<Checkbox
|
199
199
|
checked={showMoveTimes}
|
200
|
-
onChange={(e) => setShowMoveTimes(e.target.checked)}
|
200
|
+
onChange={(e) => { setShowMoveTimes(e.target.checked); }}
|
201
201
|
/>
|
202
202
|
}
|
203
203
|
label='Show elapsed time next to move'
|
@@ -85,11 +85,11 @@ const Database: React.FC<DatabaseProps> = ({
|
|
85
85
|
[minCohort, maxCohort]
|
86
86
|
);
|
87
87
|
|
88
|
-
const sortedMoves:
|
88
|
+
const sortedMoves: (ExplorerMove | LichessExplorerMove)[] = useMemo(() => {
|
89
89
|
if (!isExplorerPosition(position)) {
|
90
90
|
return position?.moves || [];
|
91
91
|
}
|
92
|
-
return Object.values(position
|
92
|
+
return Object.values(position.moves || [])
|
93
93
|
.filter((move) => {
|
94
94
|
return cohortRange.some((cohort) => {
|
95
95
|
const result = move.results[cohort] || {};
|
@@ -132,7 +132,7 @@ const Database: React.FC<DatabaseProps> = ({
|
|
132
132
|
}, [position]);
|
133
133
|
|
134
134
|
const totalGames = isExplorerPosition(position)
|
135
|
-
? getGameCount(position
|
135
|
+
? getGameCount(position.results || {}, cohortRange)
|
136
136
|
: position
|
137
137
|
? position.white + position.black + position.draws
|
138
138
|
: 0;
|
@@ -290,7 +290,7 @@ const Database: React.FC<DatabaseProps> = ({
|
|
290
290
|
fullWidth
|
291
291
|
label='Min Cohort'
|
292
292
|
value={minCohort}
|
293
|
-
onChange={(e) => setMinCohort(e.target.value)}
|
293
|
+
onChange={(e) => { setMinCohort(e.target.value); }}
|
294
294
|
>
|
295
295
|
{dojoCohorts.map((cohort) => (
|
296
296
|
<MenuItem key={cohort} value={cohort}>
|
@@ -305,7 +305,7 @@ const Database: React.FC<DatabaseProps> = ({
|
|
305
305
|
fullWidth
|
306
306
|
label='Max Cohort'
|
307
307
|
value={maxCohort}
|
308
|
-
onChange={(e) => setMaxCohort(e.target.value)}
|
308
|
+
onChange={(e) => { setMaxCohort(e.target.value); }}
|
309
309
|
>
|
310
310
|
{dojoCohorts.map((cohort, i) => (
|
311
311
|
<MenuItem
|
@@ -35,7 +35,7 @@ const Explorer = () => {
|
|
35
35
|
|
36
36
|
setFen(chess.fen());
|
37
37
|
chess.addObserver(observer);
|
38
|
-
return () => chess.removeObserver(observer);
|
38
|
+
return () => { chess.removeObserver(observer); };
|
39
39
|
}
|
40
40
|
}, [chess, setFen]);
|
41
41
|
|
@@ -70,7 +70,7 @@ const Explorer = () => {
|
|
70
70
|
<TabContext value={tab}>
|
71
71
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
72
72
|
<TabList
|
73
|
-
onChange={(_, val) => setTab(val)}
|
73
|
+
onChange={(_, val) => { setTab(val); }}
|
74
74
|
aria-label='Position database type'
|
75
75
|
>
|
76
76
|
<Tab label='Dojo Database' value='dojo' />
|
@@ -85,7 +85,7 @@ const Header: React.FC<HeaderProps> = ({
|
|
85
85
|
>
|
86
86
|
<IconButton
|
87
87
|
color={follower ? 'success' : 'info'}
|
88
|
-
onClick={() => setShowFollowDialog(!isFreeTier)}
|
88
|
+
onClick={() => { setShowFollowDialog(!isFreeTier); }}
|
89
89
|
disabled={isFreeTier}
|
90
90
|
>
|
91
91
|
{follower ? <BookmarkAddedIcon /> : <BookmarkAddIcon />}
|
@@ -98,7 +98,7 @@ const Header: React.FC<HeaderProps> = ({
|
|
98
98
|
fen={fen}
|
99
99
|
follower={follower}
|
100
100
|
open={showFollowDialog}
|
101
|
-
onClose={() => setShowFollowDialog(false)}
|
101
|
+
onClose={() => { setShowFollowDialog(false); }}
|
102
102
|
initialMinCohort={minCohort}
|
103
103
|
initialMaxCohort={maxCohort}
|
104
104
|
setFollower={setFollower}
|
@@ -196,7 +196,7 @@ const FollowDialog: React.FC<FollowDialogProps> = ({
|
|
196
196
|
fullWidth
|
197
197
|
label='Min Cohort'
|
198
198
|
value={minCohort}
|
199
|
-
onChange={(e) => setMinCohort(e.target.value)}
|
199
|
+
onChange={(e) => { setMinCohort(e.target.value); }}
|
200
200
|
>
|
201
201
|
{dojoCohorts.map((cohort) => (
|
202
202
|
<MenuItem key={cohort} value={cohort}>
|
@@ -210,7 +210,7 @@ const FollowDialog: React.FC<FollowDialogProps> = ({
|
|
210
210
|
fullWidth
|
211
211
|
label='Max Cohort'
|
212
212
|
value={maxCohort}
|
213
|
-
onChange={(e) => setMaxCohort(e.target.value)}
|
213
|
+
onChange={(e) => { setMaxCohort(e.target.value); }}
|
214
214
|
>
|
215
215
|
{dojoCohorts.map((cohort) => (
|
216
216
|
<MenuItem key={cohort} value={cohort}>
|
@@ -225,7 +225,7 @@ const FollowDialog: React.FC<FollowDialogProps> = ({
|
|
225
225
|
control={
|
226
226
|
<Checkbox
|
227
227
|
checked={disableVariations}
|
228
|
-
onChange={(e) => setDisableVariations(e.target.checked)}
|
228
|
+
onChange={(e) => { setDisableVariations(e.target.checked); }}
|
229
229
|
/>
|
230
230
|
}
|
231
231
|
label={
|
@@ -25,7 +25,7 @@ const Comment: React.FC<CommentProps> = ({ move, type, inline }) => {
|
|
25
25
|
};
|
26
26
|
|
27
27
|
chess.addObserver(observer);
|
28
|
-
return () => chess.removeObserver(observer);
|
28
|
+
return () => { chess.removeObserver(observer); };
|
29
29
|
}
|
30
30
|
}, [chess, move, setForceRender]);
|
31
31
|
|
@@ -21,13 +21,12 @@ const GameComment = () => {
|
|
21
21
|
};
|
22
22
|
|
23
23
|
chess.addObserver(observer);
|
24
|
-
return () => chess.removeObserver(observer);
|
24
|
+
return () => { chess.removeObserver(observer); };
|
25
25
|
}
|
26
26
|
}, [chess, setForceRender]);
|
27
27
|
|
28
28
|
if (
|
29
|
-
!chess?.pgn.gameComment ||
|
30
|
-
!chess.pgn.gameComment.comment ||
|
29
|
+
!chess?.pgn.gameComment.comment ||
|
31
30
|
chess.pgn.gameComment.comment.trim() === '[#]'
|
32
31
|
) {
|
33
32
|
return null;
|
@@ -7,7 +7,7 @@ import Lines from './Lines';
|
|
7
7
|
export function hasInterrupt(move: Move): boolean {
|
8
8
|
return (
|
9
9
|
(move.commentAfter?.trim().length || 0) > 0 ||
|
10
|
-
move.variations
|
10
|
+
move.variations.some((v) => v.length > 0)
|
11
11
|
);
|
12
12
|
}
|
13
13
|
|
@@ -31,7 +31,7 @@ const Line: React.FC<LineProps> = ({ line, depth, onClickMove, handleScroll }) =
|
|
31
31
|
};
|
32
32
|
|
33
33
|
chess.addObserver(observer);
|
34
|
-
return () => chess.removeObserver(observer);
|
34
|
+
return () => { chess.removeObserver(observer); };
|
35
35
|
}
|
36
36
|
}, [chess, line, setForceRender]);
|
37
37
|
|
@@ -97,7 +97,7 @@ interface LinesProps {
|
|
97
97
|
const Lines: React.FC<LinesProps> = ({ lines, depth, onClickMove, handleScroll }) => {
|
98
98
|
const [expanded, setExpanded] = useState(true);
|
99
99
|
const expandRef = useRef<HTMLHRElement>(null);
|
100
|
-
|
100
|
+
const d = depth || 0;
|
101
101
|
|
102
102
|
const onCollapse = () => {
|
103
103
|
setExpanded(false);
|
@@ -163,7 +163,7 @@ const Lines: React.FC<LinesProps> = ({ lines, depth, onClickMove, handleScroll }
|
|
163
163
|
mb: '2px',
|
164
164
|
cursor: 'pointer',
|
165
165
|
}}
|
166
|
-
onClick={() => setExpanded(true)}
|
166
|
+
onClick={() => { setExpanded(true); }}
|
167
167
|
>
|
168
168
|
<Typography variant='caption' color='background.paper'>
|
169
169
|
+{lines.length}
|
@@ -31,7 +31,7 @@ import { useChess } from '../PgnBoard';
|
|
31
31
|
|
32
32
|
export function getTextColor(move: Move, inline?: boolean): string {
|
33
33
|
for (const nag of move.nags || []) {
|
34
|
-
const color = nags[getStandardNag(nag)]
|
34
|
+
const color = nags[getStandardNag(nag)].color;
|
35
35
|
if (color) {
|
36
36
|
return color;
|
37
37
|
}
|
@@ -113,7 +113,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
|
|
113
113
|
minWidth: inline ? 'fit-content' : undefined,
|
114
114
|
display: inline ? 'inline-block' : undefined,
|
115
115
|
}}
|
116
|
-
onClick={() => onClickMove(move)}
|
116
|
+
onClick={() => { onClickMove(move); }}
|
117
117
|
onContextMenu={onRightClick}
|
118
118
|
{...longPress()}
|
119
119
|
>
|
@@ -271,7 +271,7 @@ const MoveButton: React.FC<MoveButtonProps> = ({
|
|
271
271
|
};
|
272
272
|
|
273
273
|
chess.addObserver(observer);
|
274
|
-
return () => chess.removeObserver(observer);
|
274
|
+
return () => { chess.removeObserver(observer); };
|
275
275
|
}
|
276
276
|
}, [
|
277
277
|
chess,
|
@@ -318,7 +318,7 @@ const MoveButton: React.FC<MoveButtonProps> = ({
|
|
318
318
|
setMenuAnchorEl(undefined);
|
319
319
|
};
|
320
320
|
|
321
|
-
|
321
|
+
const moveText = move.san;
|
322
322
|
|
323
323
|
if (inline) {
|
324
324
|
let text = '';
|
@@ -77,7 +77,7 @@ const MoveDisplay: React.FC<MoveProps> = ({ move, handleScroll, onClickMove }) =
|
|
77
77
|
};
|
78
78
|
|
79
79
|
chess.addObserver(observer);
|
80
|
-
return () => chess.removeObserver(observer);
|
80
|
+
return () => { chess.removeObserver(observer); };
|
81
81
|
}
|
82
82
|
}, [chess, move, setForceRender, setNeedReminder]);
|
83
83
|
|
@@ -20,7 +20,7 @@ const Result = () => {
|
|
20
20
|
};
|
21
21
|
|
22
22
|
chess.addObserver(observer);
|
23
|
-
return () => chess.removeObserver(observer);
|
23
|
+
return () => { chess.removeObserver(observer); };
|
24
24
|
}
|
25
25
|
}, [chess, setForceRender]);
|
26
26
|
|
@@ -46,7 +46,7 @@ const Variation: React.FC<VariationProps> = ({ handleScroll, onClickMove }) => {
|
|
46
46
|
};
|
47
47
|
|
48
48
|
chess.addObserver(observer);
|
49
|
-
return () => chess.removeObserver(observer);
|
49
|
+
return () => { chess.removeObserver(observer); };
|
50
50
|
}
|
51
51
|
}, [chess, setForceRender]);
|
52
52
|
|
@@ -102,7 +102,7 @@ const IncorrectMoveHint: React.FC<HintSectionProps> = ({
|
|
102
102
|
disableElevation
|
103
103
|
color='error'
|
104
104
|
sx={{ flexGrow: 1 }}
|
105
|
-
onClick={() => onRetry(board, chess)}
|
105
|
+
onClick={() => { onRetry(board, chess); }}
|
106
106
|
>
|
107
107
|
Retry
|
108
108
|
<br />
|
@@ -157,7 +157,7 @@ const CorrectMoveHint: React.FC<HintSectionProps> = ({
|
|
157
157
|
disableElevation
|
158
158
|
color='success'
|
159
159
|
sx={{ flexGrow: 1 }}
|
160
|
-
onClick={() => onNext(board, chess)}
|
160
|
+
onClick={() => { onNext(board, chess); }}
|
161
161
|
>
|
162
162
|
Next
|
163
163
|
<br />
|
@@ -243,7 +243,7 @@ const CompleteHint: React.FC<HintSectionProps> = ({
|
|
243
243
|
variant='contained'
|
244
244
|
disableElevation
|
245
245
|
sx={{ flexGrow: 1 }}
|
246
|
-
onClick={() => onRestart(board, chess)}
|
246
|
+
onClick={() => { onRestart(board, chess); }}
|
247
247
|
>
|
248
248
|
Restart
|
249
249
|
</Button>
|
@@ -112,7 +112,7 @@ const PuzzleBoard: React.FC<PuzzleBoardProps> = ({
|
|
112
112
|
chess.lastMove() === chess.currentMove() ||
|
113
113
|
chess.hasNagInRange(10, 140)
|
114
114
|
) {
|
115
|
-
|
115
|
+
onComplete(board, chess); return;
|
116
116
|
}
|
117
117
|
setStatus(Status.CorrectMove);
|
118
118
|
setLastCorrectMove(chess.currentMove());
|
@@ -141,11 +141,11 @@ const PuzzleBoard: React.FC<PuzzleBoardProps> = ({
|
|
141
141
|
const onNext = (board: BoardApi | undefined, chess: Chess) => {
|
142
142
|
const nextMove = chess.nextMove();
|
143
143
|
if (!nextMove) {
|
144
|
-
|
144
|
+
onComplete(board, chess); return;
|
145
145
|
}
|
146
146
|
chess.seek(nextMove);
|
147
147
|
if (chess.lastMove() === nextMove || chess.hasNagInRange(10, 140, nextMove)) {
|
148
|
-
|
148
|
+
onComplete(board, chess); return;
|
149
149
|
}
|
150
150
|
|
151
151
|
board?.move(nextMove.from, nextMove.to);
|
@@ -151,7 +151,7 @@ const AvailabilityBooker: React.FC<AvailabilityBookerProps> = ({ availability })
|
|
151
151
|
|
152
152
|
const confirmBooking = () => {
|
153
153
|
if (isGroup) {
|
154
|
-
|
154
|
+
confirmGroupBooking(); return;
|
155
155
|
}
|
156
156
|
confirmSoloBooking();
|
157
157
|
};
|
@@ -173,7 +173,7 @@ const AvailabilityBooker: React.FC<AvailabilityBookerProps> = ({ availability })
|
|
173
173
|
<Button
|
174
174
|
data-cy='cancel-button'
|
175
175
|
color='inherit'
|
176
|
-
onClick={() => navigate('/calendar')}
|
176
|
+
onClick={() => { navigate('/calendar'); }}
|
177
177
|
disabled={request.status === RequestStatus.Loading}
|
178
178
|
>
|
179
179
|
Cancel
|
@@ -273,9 +273,9 @@ const AvailabilityBooker: React.FC<AvailabilityBookerProps> = ({ availability })
|
|
273
273
|
name='radio-buttons-group'
|
274
274
|
value={selectedType}
|
275
275
|
onChange={(event) =>
|
276
|
-
setSelectedType(
|
276
|
+
{ setSelectedType(
|
277
277
|
event.target.value as AvailabilityType,
|
278
|
-
)
|
278
|
+
); }
|
279
279
|
}
|
280
280
|
>
|
281
281
|
{availability.types?.map((t) => (
|
@@ -295,7 +295,7 @@ const AvailabilityBooker: React.FC<AvailabilityBookerProps> = ({ availability })
|
|
295
295
|
<TimePicker
|
296
296
|
label='Start Time'
|
297
297
|
value={startTime}
|
298
|
-
onChange={(value) => setStartTime(value)}
|
298
|
+
onChange={(value) => { setStartTime(value); }}
|
299
299
|
slotProps={{
|
300
300
|
textField: {
|
301
301
|
fullWidth: true,
|
@@ -437,7 +437,7 @@ export default function CalendarPage() {
|
|
437
437
|
<Snackbar
|
438
438
|
open={canceled}
|
439
439
|
autoHideDuration={6000}
|
440
|
-
onClose={() => setCanceled(false)}
|
440
|
+
onClose={() => { setCanceled(false); }}
|
441
441
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
442
442
|
message='Meeting canceled'
|
443
443
|
/>
|
@@ -85,7 +85,7 @@ const CoachingBooker: React.FC<CoachingBookerProps> = ({ event }) => {
|
|
85
85
|
<Button
|
86
86
|
data-cy='cancel-button'
|
87
87
|
color='inherit'
|
88
|
-
onClick={() => navigate('/calendar')}
|
88
|
+
onClick={() => { navigate('/calendar'); }}
|
89
89
|
disabled={request.status === RequestStatus.Loading}
|
90
90
|
>
|
91
91
|
Cancel
|
@@ -7,9 +7,9 @@ import { EventType } from '../database/event';
|
|
7
7
|
import AvailabilityBooker from './AvailabilityBooker';
|
8
8
|
import CoachingBooker from './CoachingBooker';
|
9
9
|
|
10
|
-
|
10
|
+
interface EventBookerProps {
|
11
11
|
id: string;
|
12
|
-
}
|
12
|
+
}
|
13
13
|
|
14
14
|
const EventBooker = () => {
|
15
15
|
const navigate = useNavigate();
|
@@ -33,7 +33,7 @@ const EventBooker = () => {
|
|
33
33
|
variant='filled'
|
34
34
|
severity='error'
|
35
35
|
sx={{ width: '100%' }}
|
36
|
-
onClose={() => navigate('/calendar')}
|
36
|
+
onClose={() => { navigate('/calendar'); }}
|
37
37
|
>
|
38
38
|
This event cannot be found. It is either fully booked, deleted or not
|
39
39
|
available to your cohort.
|
@@ -214,7 +214,7 @@ const AvailabilityEditor: React.FC<AvailabilityEditorProps> = ({ editor }) => {
|
|
214
214
|
<Checkbox
|
215
215
|
checked={allAvailabilityTypes}
|
216
216
|
onChange={(event) =>
|
217
|
-
setAllAvailabilityTypes(event.target.checked)
|
217
|
+
{ setAllAvailabilityTypes(event.target.checked); }
|
218
218
|
}
|
219
219
|
/>
|
220
220
|
}
|
@@ -231,10 +231,10 @@ const AvailabilityEditor: React.FC<AvailabilityEditorProps> = ({ editor }) => {
|
|
231
231
|
availabilityTypes[type]
|
232
232
|
}
|
233
233
|
onChange={(event) =>
|
234
|
-
setAvailabilityType(
|
234
|
+
{ setAvailabilityType(
|
235
235
|
type,
|
236
236
|
event.target.checked,
|
237
|
-
)
|
237
|
+
); }
|
238
238
|
}
|
239
239
|
/>
|
240
240
|
}
|
@@ -67,7 +67,7 @@ export function validateCoachingEditor(
|
|
67
67
|
errors.location = 'This field is required';
|
68
68
|
}
|
69
69
|
|
70
|
-
let maxParticipants
|
70
|
+
let maxParticipants = -1;
|
71
71
|
if (!editor.maxParticipants.trim()) {
|
72
72
|
errors.maxParticipants = 'This field is required';
|
73
73
|
} else {
|
@@ -83,7 +83,7 @@ export function validateCoachingEditor(
|
|
83
83
|
}
|
84
84
|
}
|
85
85
|
|
86
|
-
let fullPrice
|
86
|
+
let fullPrice = -1;
|
87
87
|
if (!editor.fullPrice.trim()) {
|
88
88
|
errors.fullPrice = 'This field is required';
|
89
89
|
} else {
|
@@ -94,7 +94,7 @@ export function validateCoachingEditor(
|
|
94
94
|
}
|
95
95
|
}
|
96
96
|
|
97
|
-
let currentPrice
|
97
|
+
let currentPrice = -1;
|
98
98
|
if (editor.currentPrice.trim()) {
|
99
99
|
const [price, error] = validatePrice(editor.currentPrice);
|
100
100
|
currentPrice = price;
|
@@ -170,7 +170,7 @@ const CoachingEditor: React.FC<CoachingEditorProps> = ({ editor }) => {
|
|
170
170
|
<Button
|
171
171
|
color='inherit'
|
172
172
|
size='small'
|
173
|
-
onClick={() => navigate('/coach')}
|
173
|
+
onClick={() => { navigate('/coach'); }}
|
174
174
|
>
|
175
175
|
Open Portal
|
176
176
|
</Button>
|
@@ -259,7 +259,7 @@ const CoachingEditor: React.FC<CoachingEditorProps> = ({ editor }) => {
|
|
259
259
|
control={
|
260
260
|
<Checkbox
|
261
261
|
checked={hideParticipants}
|
262
|
-
onChange={(e) => setHideParticipants(e.target.checked)}
|
262
|
+
onChange={(e) => { setHideParticipants(e.target.checked); }}
|
263
263
|
/>
|
264
264
|
}
|
265
265
|
label='Hide participant list when booking? If checked, users will only see other participants after they have booked.'
|
@@ -281,7 +281,7 @@ const CoachingEditor: React.FC<CoachingEditorProps> = ({ editor }) => {
|
|
281
281
|
control={
|
282
282
|
<Checkbox
|
283
283
|
checked={bookableByFreeUsers}
|
284
|
-
onChange={(e) => setBookableByFreeUsers(e.target.checked)}
|
284
|
+
onChange={(e) => { setBookableByFreeUsers(e.target.checked); }}
|
285
285
|
/>
|
286
286
|
}
|
287
287
|
label='Allow free-tier users to see and book this event'
|
@@ -296,7 +296,7 @@ const CoachingEditor: React.FC<CoachingEditorProps> = ({ editor }) => {
|
|
296
296
|
label='Full Price'
|
297
297
|
variant='outlined'
|
298
298
|
value={fullPrice}
|
299
|
-
onChange={(e) => setFullPrice(e.target.value)}
|
299
|
+
onChange={(e) => { setFullPrice(e.target.value); }}
|
300
300
|
error={Boolean(errors.fullPrice)}
|
301
301
|
helperText={errors.fullPrice}
|
302
302
|
InputProps={{
|
@@ -310,7 +310,7 @@ const CoachingEditor: React.FC<CoachingEditorProps> = ({ editor }) => {
|
|
310
310
|
label='Sale Price'
|
311
311
|
variant='outlined'
|
312
312
|
value={currentPrice}
|
313
|
-
onChange={(e) => setCurrentPrice(e.target.value)}
|
313
|
+
onChange={(e) => { setCurrentPrice(e.target.value); }}
|
314
314
|
error={Boolean(errors.currentPrice)}
|
315
315
|
helperText={
|
316
316
|
errors.currentPrice ||
|
@@ -107,7 +107,7 @@ const DojoEventEditor: React.FC<DojoEventEditorProps> = ({ editor }) => {
|
|
107
107
|
control={
|
108
108
|
<Checkbox
|
109
109
|
checked={hideFromPublicDiscord}
|
110
|
-
onChange={(e) => setHideFromPublicDiscord(e.target.checked)}
|
110
|
+
onChange={(e) => { setHideFromPublicDiscord(e.target.checked); }}
|
111
111
|
/>
|
112
112
|
}
|
113
113
|
/>
|
@@ -164,7 +164,7 @@ const EventEditor: React.FC<EventEditorProps> = ({ scheduler }) => {
|
|
164
164
|
<RadioGroup
|
165
165
|
value={editor.type}
|
166
166
|
onChange={(e) =>
|
167
|
-
editor.setType(e.target.value as EventType)
|
167
|
+
{ editor.setType(e.target.value as EventType); }
|
168
168
|
}
|
169
169
|
>
|
170
170
|
<FormControlLabel
|
@@ -38,7 +38,7 @@ const CohortsFormSection: React.FC<CohortsFormSectionProps> = ({
|
|
38
38
|
control={
|
39
39
|
<Checkbox
|
40
40
|
checked={allCohorts}
|
41
|
-
onChange={(event) => setAllCohorts(event.target.checked)}
|
41
|
+
onChange={(event) => { setAllCohorts(event.target.checked); }}
|
42
42
|
/>
|
43
43
|
}
|
44
44
|
label='All Cohorts'
|
@@ -52,7 +52,7 @@ const CohortsFormSection: React.FC<CohortsFormSectionProps> = ({
|
|
52
52
|
data-cy={`cohort-checkbox-${cohort}`}
|
53
53
|
checked={allCohorts || cohorts[cohort]}
|
54
54
|
onChange={(event) =>
|
55
|
-
setCohort(cohort, event.target.checked)
|
55
|
+
{ setCohort(cohort, event.target.checked); }
|
56
56
|
}
|
57
57
|
/>
|
58
58
|
}
|
@@ -28,7 +28,7 @@ const DescriptionFormSection: React.FC<DescriptionFormSectionProps> = ({
|
|
28
28
|
minRows={3}
|
29
29
|
maxRows={3}
|
30
30
|
value={description}
|
31
|
-
onChange={(event) => setDescription(event.target.value)}
|
31
|
+
onChange={(event) => { setDescription(event.target.value); }}
|
32
32
|
error={Boolean(error)}
|
33
33
|
helperText={error}
|
34
34
|
/>
|
@@ -29,7 +29,7 @@ const LocationFormSection: React.FC<LocationFormSectionProps> = ({
|
|
29
29
|
label='Location'
|
30
30
|
variant='outlined'
|
31
31
|
value={location}
|
32
|
-
onChange={(event) => setLocation(event.target.value)}
|
32
|
+
onChange={(event) => { setLocation(event.target.value); }}
|
33
33
|
helperText={error || helperText}
|
34
34
|
error={Boolean(error)}
|
35
35
|
/>
|
@@ -31,7 +31,7 @@ const MaxParticipantsFormSection: React.FC<MaxParticipantsFormSectionProps> = ({
|
|
31
31
|
inputMode: 'numeric',
|
32
32
|
pattern: '[0-9]*',
|
33
33
|
}}
|
34
|
-
onChange={(event) => setMaxParticipants(event.target.value)}
|
34
|
+
onChange={(event) => { setMaxParticipants(event.target.value); }}
|
35
35
|
helperText={error || helperText}
|
36
36
|
error={Boolean(error)}
|
37
37
|
/>
|
@@ -41,7 +41,7 @@ const TimesFormSection: React.FC<TimesFormSectionProps> = ({
|
|
41
41
|
<DateTimePicker
|
42
42
|
label='Start Time'
|
43
43
|
value={start}
|
44
|
-
onChange={(value) => setStart(value)}
|
44
|
+
onChange={(value) => { setStart(value); }}
|
45
45
|
slotProps={{
|
46
46
|
textField: {
|
47
47
|
id: 'start-time',
|
@@ -57,7 +57,7 @@ const TimesFormSection: React.FC<TimesFormSectionProps> = ({
|
|
57
57
|
<DateTimePicker
|
58
58
|
label='End Time'
|
59
59
|
value={end}
|
60
|
-
onChange={(value) => setEnd(value)}
|
60
|
+
onChange={(value) => { setEnd(value); }}
|
61
61
|
slotProps={{
|
62
62
|
textField: {
|
63
63
|
id: 'end-time',
|
@@ -26,7 +26,7 @@ const TitleFormSection: React.FC<TitleFormSectionProps> = ({
|
|
26
26
|
label='Title'
|
27
27
|
variant='outlined'
|
28
28
|
value={title}
|
29
|
-
onChange={(event) => setTitle(event.target.value)}
|
29
|
+
onChange={(event) => { setTitle(event.target.value); }}
|
30
30
|
error={Boolean(error)}
|
31
31
|
helperText={error}
|
32
32
|
sx={{ mt: 2 }}
|
@@ -222,12 +222,12 @@ export default function useEventEditor(
|
|
222
222
|
const [availabilityTypes, setAvailabilityTypes] = useState<
|
223
223
|
Record<AvailabilityType, boolean>
|
224
224
|
>(
|
225
|
-
Object.values(AvailabilityType).reduce(
|
225
|
+
Object.values(AvailabilityType).reduce<Record<AvailabilityType, boolean>>(
|
226
226
|
(map, type) => {
|
227
227
|
map[type] = false;
|
228
228
|
return map;
|
229
229
|
},
|
230
|
-
{}
|
230
|
+
{},
|
231
231
|
),
|
232
232
|
);
|
233
233
|
const setAvailabilityType = useCallback(
|
@@ -243,13 +243,13 @@ export default function useEventEditor(
|
|
243
243
|
const userCohortIndex = dojoCohorts.findIndex((c) => c === user.dojoCohort);
|
244
244
|
const [allCohorts, setAllCohorts] = useState(false);
|
245
245
|
const [cohorts, setCohorts] = useState<Record<string, boolean>>(
|
246
|
-
dojoCohorts.reduce(
|
246
|
+
dojoCohorts.reduce<Record<string, boolean>>(
|
247
247
|
(map, cohort, index) => {
|
248
248
|
map[cohort] =
|
249
249
|
userCohortIndex >= 0 && Math.abs(index - userCohortIndex) <= 1;
|
250
250
|
return map;
|
251
251
|
},
|
252
|
-
{}
|
252
|
+
{},
|
253
253
|
),
|
254
254
|
);
|
255
255
|
const setCohort = useCallback(
|
@@ -287,12 +287,12 @@ export default function useEventEditor(
|
|
287
287
|
|
288
288
|
const onChangeEventType = useCallback(
|
289
289
|
(value: EventType) => {
|
290
|
-
const allFalseCohorts = dojoCohorts.reduce(
|
290
|
+
const allFalseCohorts = dojoCohorts.reduce<Record<string, boolean>>(
|
291
291
|
(map, cohort) => {
|
292
292
|
map[cohort] = false;
|
293
293
|
return map;
|
294
294
|
},
|
295
|
-
{}
|
295
|
+
{},
|
296
296
|
);
|
297
297
|
|
298
298
|
setType(value);
|
@@ -312,14 +312,14 @@ export default function useEventEditor(
|
|
312
312
|
);
|
313
313
|
} else {
|
314
314
|
setCohorts(
|
315
|
-
dojoCohorts.reduce(
|
315
|
+
dojoCohorts.reduce<Record<string, boolean>>(
|
316
316
|
(map, cohort, index) => {
|
317
317
|
map[cohort] =
|
318
318
|
userCohortIndex >= 0 &&
|
319
319
|
Math.abs(index - userCohortIndex) <= 1;
|
320
320
|
return map;
|
321
321
|
},
|
322
|
-
{}
|
322
|
+
{},
|
323
323
|
),
|
324
324
|
);
|
325
325
|
}
|
@@ -347,12 +347,12 @@ export default function useEventEditor(
|
|
347
347
|
|
348
348
|
const originalCohorts: string[] = initialEvent?.cohorts || [];
|
349
349
|
if (originalCohorts.length > 0) {
|
350
|
-
const allFalseCohorts = dojoCohorts.reduce(
|
350
|
+
const allFalseCohorts = dojoCohorts.reduce<Record<string, boolean>>(
|
351
351
|
(map, cohort) => {
|
352
352
|
map[cohort] = false;
|
353
353
|
return map;
|
354
354
|
},
|
355
|
-
{}
|
355
|
+
{},
|
356
356
|
);
|
357
357
|
setCohorts(() =>
|
358
358
|
originalCohorts.reduce(
|
@@ -107,7 +107,7 @@ const CoachingViewer: React.FC<CoachingViewerProps> = ({ processedEvent }) => {
|
|
107
107
|
{isOwner || isParticipant ? (
|
108
108
|
<Button
|
109
109
|
variant='contained'
|
110
|
-
onClick={() => navigate(`/meeting/${event.id}`)}
|
110
|
+
onClick={() => { navigate(`/meeting/${event.id}`); }}
|
111
111
|
>
|
112
112
|
View Details
|
113
113
|
</Button>
|
@@ -99,7 +99,7 @@ const LigaTournamentViewer: React.FC<LigaTournamentViewerProps> = ({
|
|
99
99
|
fen: ligaTournament.fen.trim(),
|
100
100
|
viewOnly: true,
|
101
101
|
}}
|
102
|
-
onInitialize={(board) => board.redrawAll()}
|
102
|
+
onInitialize={(board) => { board.redrawAll(); }}
|
103
103
|
/>
|
104
104
|
)}
|
105
105
|
</Box>
|
@@ -44,7 +44,7 @@ const MeetingViewer: React.FC<MeetingViewerProps> = ({ processedEvent }) => {
|
|
44
44
|
)}
|
45
45
|
</Stack>
|
46
46
|
|
47
|
-
<Button variant='contained' onClick={() => navigate(`/meeting/${event.id}`)}>
|
47
|
+
<Button variant='contained' onClick={() => { navigate(`/meeting/${event.id}`); }}>
|
48
48
|
View Details
|
49
49
|
</Button>
|
50
50
|
</Stack>
|
@@ -82,44 +82,44 @@ export const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
|
82
82
|
paddingLeft: theme.spacing(1),
|
83
83
|
}));
|
84
84
|
|
85
|
-
const initialFilterTypes = Object.values(AvailabilityType).reduce(
|
85
|
+
const initialFilterTypes = Object.values(AvailabilityType).reduce<Record<AvailabilityType, boolean>>(
|
86
86
|
(map, type) => {
|
87
87
|
map[type] = false;
|
88
88
|
return map;
|
89
89
|
},
|
90
|
-
{}
|
90
|
+
{},
|
91
91
|
);
|
92
92
|
|
93
|
-
const initialFilterCohorts = dojoCohorts.reduce(
|
93
|
+
const initialFilterCohorts = dojoCohorts.reduce<Record<string, boolean>>(
|
94
94
|
(map, cohort) => {
|
95
95
|
map[cohort] = false;
|
96
96
|
return map;
|
97
97
|
},
|
98
|
-
{}
|
98
|
+
{},
|
99
99
|
);
|
100
100
|
|
101
|
-
const initialFilterTournamentTypes = Object.values(TournamentType).reduce(
|
101
|
+
const initialFilterTournamentTypes = Object.values(TournamentType).reduce<Record<TournamentType, boolean>>(
|
102
102
|
(map, type) => {
|
103
103
|
map[type] = true;
|
104
104
|
return map;
|
105
105
|
},
|
106
|
-
{}
|
106
|
+
{},
|
107
107
|
);
|
108
108
|
|
109
|
-
const initialFilterTournamentTimeControls = Object.values(TimeControlType).reduce(
|
109
|
+
const initialFilterTournamentTimeControls = Object.values(TimeControlType).reduce<Record<TimeControlType, boolean>>(
|
110
110
|
(map, type) => {
|
111
111
|
map[type] = true;
|
112
112
|
return map;
|
113
113
|
},
|
114
|
-
{}
|
114
|
+
{},
|
115
115
|
);
|
116
116
|
|
117
|
-
const initialFilterTournamentPositions = Object.values(PositionType).reduce(
|
117
|
+
const initialFilterTournamentPositions = Object.values(PositionType).reduce<Record<PositionType, boolean>>(
|
118
118
|
(m, t) => {
|
119
119
|
m[t] = true;
|
120
120
|
return m;
|
121
121
|
},
|
122
|
-
{}
|
122
|
+
{},
|
123
123
|
);
|
124
124
|
|
125
125
|
export interface Filters {
|
@@ -442,7 +442,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
442
442
|
<Checkbox
|
443
443
|
checked={filters.availabilities}
|
444
444
|
onChange={(event) =>
|
445
|
-
filters.setAvailabilities(event.target.checked)
|
445
|
+
{ filters.setAvailabilities(event.target.checked); }
|
446
446
|
}
|
447
447
|
/>
|
448
448
|
}
|
@@ -453,7 +453,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
453
453
|
<Checkbox
|
454
454
|
checked={filters.meetings}
|
455
455
|
onChange={(event) =>
|
456
|
-
filters.setMeetings(event.target.checked)
|
456
|
+
{ filters.setMeetings(event.target.checked); }
|
457
457
|
}
|
458
458
|
/>
|
459
459
|
}
|
@@ -482,7 +482,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
482
482
|
<Checkbox
|
483
483
|
checked={filters.dojoEvents}
|
484
484
|
onChange={(event) =>
|
485
|
-
filters.setDojoEvents(event.target.checked)
|
485
|
+
{ filters.setDojoEvents(event.target.checked); }
|
486
486
|
}
|
487
487
|
color='success'
|
488
488
|
/>
|
@@ -494,7 +494,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
494
494
|
<Checkbox
|
495
495
|
checked={filters.coaching}
|
496
496
|
onChange={(event) =>
|
497
|
-
filters.setCoaching(event.target.checked)
|
497
|
+
{ filters.setCoaching(event.target.checked); }
|
498
498
|
}
|
499
499
|
color='coaching'
|
500
500
|
/>
|
@@ -525,10 +525,10 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
525
525
|
filters.tournamentTimeControls[type]
|
526
526
|
}
|
527
527
|
onChange={(event) =>
|
528
|
-
onChangeTournamentTimeControls(
|
528
|
+
{ onChangeTournamentTimeControls(
|
529
529
|
type,
|
530
530
|
event.target.checked,
|
531
|
-
)
|
531
|
+
); }
|
532
532
|
}
|
533
533
|
disabled={!filters.dojoEvents}
|
534
534
|
color='warning'
|
@@ -549,7 +549,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
549
549
|
<Checkbox
|
550
550
|
checked={filters.allTypes}
|
551
551
|
onChange={(event) =>
|
552
|
-
filters.setAllTypes(event.target.checked)
|
552
|
+
{ filters.setAllTypes(event.target.checked); }
|
553
553
|
}
|
554
554
|
sx={{
|
555
555
|
color: 'error.dark',
|
@@ -568,7 +568,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
568
568
|
<Checkbox
|
569
569
|
checked={filters.allTypes || filters.types[type]}
|
570
570
|
onChange={(event) =>
|
571
|
-
onChangeType(type, event.target.checked)
|
571
|
+
{ onChangeType(type, event.target.checked); }
|
572
572
|
}
|
573
573
|
sx={{
|
574
574
|
color: 'error.dark',
|
@@ -592,7 +592,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
592
592
|
<Checkbox
|
593
593
|
checked={filters.allCohorts}
|
594
594
|
onChange={(event) =>
|
595
|
-
filters.setAllCohorts(event.target.checked)
|
595
|
+
{ filters.setAllCohorts(event.target.checked); }
|
596
596
|
}
|
597
597
|
sx={{
|
598
598
|
color: 'error.dark',
|
@@ -613,7 +613,7 @@ export const CalendarFilters: React.FC<CalendarFiltersProps> = ({ filters }) =>
|
|
613
613
|
filters.allCohorts || filters.cohorts[cohort]
|
614
614
|
}
|
615
615
|
onChange={(event) =>
|
616
|
-
onChangeCohort(cohort, event.target.checked)
|
616
|
+
{ onChangeCohort(cohort, event.target.checked); }
|
617
617
|
}
|
618
618
|
sx={{
|
619
619
|
color: 'error.dark',
|
@@ -68,8 +68,8 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
68
68
|
const browserDefaultLabel =
|
69
69
|
timezoneOffset > 0 ? `UTC-${timezoneOffset}` : `UTC+${Math.abs(timezoneOffset)}`;
|
70
70
|
|
71
|
-
|
72
|
-
|
71
|
+
const minHourNum = minHour?.hour || 0;
|
72
|
+
const maxHourNum = (maxHour?.hour || 23) + 1;
|
73
73
|
|
74
74
|
return (
|
75
75
|
<Stack spacing={2.5}>
|
@@ -78,7 +78,7 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
78
78
|
<RadioGroup
|
79
79
|
row
|
80
80
|
value={timeFormat}
|
81
|
-
onChange={(e) => onChangeTimeFormat(e.target.value as TimeFormat)}
|
81
|
+
onChange={(e) => { onChangeTimeFormat(e.target.value as TimeFormat); }}
|
82
82
|
>
|
83
83
|
<FormControlLabel
|
84
84
|
value={TimeFormat.TwelveHour}
|
@@ -108,7 +108,7 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
108
108
|
select
|
109
109
|
data-cy='timezone-selector'
|
110
110
|
value={timezone}
|
111
|
-
onChange={(e) => onChangeTimezone(e.target.value)}
|
111
|
+
onChange={(e) => { onChangeTimezone(e.target.value); }}
|
112
112
|
size='small'
|
113
113
|
>
|
114
114
|
<MenuItem value={DefaultTimezone}>
|
@@ -121,7 +121,7 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
121
121
|
label='Week Start'
|
122
122
|
select
|
123
123
|
value={weekStartOn}
|
124
|
-
onChange={(e) => setWeekStartOn(parseInt(e.target.value) as WeekDays)}
|
124
|
+
onChange={(e) => { setWeekStartOn(parseInt(e.target.value) as WeekDays); }}
|
125
125
|
size='small'
|
126
126
|
>
|
127
127
|
<MenuItem value={0}>Sunday</MenuItem>
|
@@ -138,7 +138,7 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
138
138
|
views={['hours']}
|
139
139
|
ampm={timeFormat === TimeFormat.TwelveHour}
|
140
140
|
value={minHour}
|
141
|
-
onChange={(v) => setMinHour(v)}
|
141
|
+
onChange={(v) => { setMinHour(v); }}
|
142
142
|
maxTime={maxHour}
|
143
143
|
slotProps={{
|
144
144
|
textField: {
|
@@ -155,7 +155,7 @@ const TimezoneFilter: React.FC<TimezoneFilterProps> = ({ filters }) => {
|
|
155
155
|
views={['hours']}
|
156
156
|
ampm={timeFormat === TimeFormat.TwelveHour}
|
157
157
|
value={maxHour}
|
158
|
-
onChange={(v) => setMaxHour(v)}
|
158
|
+
onChange={(v) => { setMaxHour(v); }}
|
159
159
|
minTime={minHour}
|
160
160
|
slotProps={{
|
161
161
|
textField: {
|
@@ -33,9 +33,9 @@ import MemberCountChip from './MemberCountChip';
|
|
33
33
|
import ScoreboardTab from './ScoreboardTab';
|
34
34
|
import UrlChip from './UrlChip';
|
35
35
|
|
36
|
-
export
|
36
|
+
export interface ClubDetailsParams {
|
37
37
|
id: string;
|
38
|
-
}
|
38
|
+
}
|
39
39
|
|
40
40
|
const ClubDetailsPage = () => {
|
41
41
|
const auth = useAuth();
|
@@ -144,14 +144,14 @@ const ClubDetailsPage = () => {
|
|
144
144
|
<Snackbar
|
145
145
|
open={Boolean(snackbarText)}
|
146
146
|
autoHideDuration={5000}
|
147
|
-
onClose={() => setSnackbarText('')}
|
147
|
+
onClose={() => { setSnackbarText(''); }}
|
148
148
|
message={snackbarText}
|
149
149
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
150
150
|
/>
|
151
151
|
|
152
152
|
<UpsellDialog
|
153
153
|
open={Boolean(upsellAction)}
|
154
|
-
onClose={() => setUpsellAction('')}
|
154
|
+
onClose={() => { setUpsellAction(''); }}
|
155
155
|
currentAction={upsellAction}
|
156
156
|
/>
|
157
157
|
|
@@ -182,7 +182,7 @@ const ClubDetailsPage = () => {
|
|
182
182
|
<Button
|
183
183
|
variant='contained'
|
184
184
|
onClick={() =>
|
185
|
-
navigate(`/clubs/${club.id}/edit`)
|
185
|
+
{ navigate(`/clubs/${club.id}/edit`); }
|
186
186
|
}
|
187
187
|
>
|
188
188
|
Edit Settings
|
@@ -228,7 +228,7 @@ const ClubDetailsPage = () => {
|
|
228
228
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
229
229
|
<Tabs
|
230
230
|
value={searchParams.get('view')}
|
231
|
-
onChange={(_, t) => setSearchParams({ view: t })}
|
231
|
+
onChange={(_, t) => { setSearchParams({ view: t }); }}
|
232
232
|
aria-label='profile tabs'
|
233
233
|
variant='scrollable'
|
234
234
|
>
|
@@ -269,7 +269,7 @@ const ClubDetailsPage = () => {
|
|
269
269
|
clubName={club.name}
|
270
270
|
open={showJoinRequestDialog}
|
271
271
|
onSuccess={onSuccessfulJoinRequest}
|
272
|
-
onClose={() => setShowJoinRequestDialog(false)}
|
272
|
+
onClose={() => { setShowJoinRequestDialog(false); }}
|
273
273
|
/>
|
274
274
|
|
275
275
|
<LeaveClubDialog
|
@@ -278,7 +278,7 @@ const ClubDetailsPage = () => {
|
|
278
278
|
approvalRequired={club.approvalRequired}
|
279
279
|
open={showLeaveDialog}
|
280
280
|
onSuccess={onLeaveClubConfirm}
|
281
|
-
onClose={() => setShowLeaveDialog(false)}
|
281
|
+
onClose={() => { setShowLeaveDialog(false); }}
|
282
282
|
/>
|
283
283
|
</Stack>
|
284
284
|
)}
|
@@ -102,7 +102,7 @@ export const ClubFilterEditor: React.FC<ClubFilterEditorProps> = ({ filters }) =
|
|
102
102
|
<TextField
|
103
103
|
label='Search...'
|
104
104
|
value={filters.search}
|
105
|
-
onChange={(e) => filters.setSearch(e.target.value)}
|
105
|
+
onChange={(e) => { filters.setSearch(e.target.value); }}
|
106
106
|
sx={{ flexGrow: 1 }}
|
107
107
|
/>
|
108
108
|
|
@@ -112,7 +112,7 @@ export const ClubFilterEditor: React.FC<ClubFilterEditorProps> = ({ filters }) =
|
|
112
112
|
label='Sort By'
|
113
113
|
value={filters.sortMethod}
|
114
114
|
onChange={(e) =>
|
115
|
-
filters.setSortMethod(e.target.value as ClubSortMethod)
|
115
|
+
{ filters.setSortMethod(e.target.value as ClubSortMethod); }
|
116
116
|
}
|
117
117
|
>
|
118
118
|
{Object.values(ClubSortMethod).map((method) => (
|
@@ -127,7 +127,7 @@ export const ClubFilterEditor: React.FC<ClubFilterEditorProps> = ({ filters }) =
|
|
127
127
|
<RadioGroup
|
128
128
|
value={filters.sortDirection}
|
129
129
|
onChange={(e) =>
|
130
|
-
filters.setSortDirection(e.target.value as 'asc' | 'desc')
|
130
|
+
{ filters.setSortDirection(e.target.value as 'asc' | 'desc'); }
|
131
131
|
}
|
132
132
|
row
|
133
133
|
>
|
@@ -63,7 +63,7 @@ export const ListClubItem: React.FC<ListClubItemProps> = ({ club, sx }) => {
|
|
63
63
|
alignItems: 'start',
|
64
64
|
justifyContent: 'start',
|
65
65
|
}}
|
66
|
-
onClick={() => navigate(`/clubs/${club.id}`)}
|
66
|
+
onClick={() => { navigate(`/clubs/${club.id}`); }}
|
67
67
|
>
|
68
68
|
<CardHeader
|
69
69
|
sx={{ pb: 1 }}
|
@@ -60,7 +60,7 @@ const ClubJoinRequestDialog: React.FC<ClubJoinRequestDialogProps> = ({
|
|
60
60
|
<TextField
|
61
61
|
label='Optionally explain your request'
|
62
62
|
value={notes}
|
63
|
-
onChange={(e) => setNotes(e.target.value)}
|
63
|
+
onChange={(e) => { setNotes(e.target.value); }}
|
64
64
|
multiline
|
65
65
|
minRows={3}
|
66
66
|
fullWidth
|
@@ -203,7 +203,7 @@ const CreateClubPage = () => {
|
|
203
203
|
label='Name'
|
204
204
|
required
|
205
205
|
value={name}
|
206
|
-
onChange={(e) => setName(e.target.value)}
|
206
|
+
onChange={(e) => { setName(e.target.value); }}
|
207
207
|
error={Boolean(errors.name)}
|
208
208
|
helperText={errors.name}
|
209
209
|
/>
|
@@ -215,7 +215,7 @@ const CreateClubPage = () => {
|
|
215
215
|
multiline
|
216
216
|
minRows={3}
|
217
217
|
value={shortDescription}
|
218
|
-
onChange={(e) => setShortDescription(e.target.value)}
|
218
|
+
onChange={(e) => { setShortDescription(e.target.value); }}
|
219
219
|
error={
|
220
220
|
Boolean(errors.shortDescription) ||
|
221
221
|
shortDescription.length > 300
|
@@ -233,7 +233,7 @@ const CreateClubPage = () => {
|
|
233
233
|
multiline
|
234
234
|
minRows={5}
|
235
235
|
value={description}
|
236
|
-
onChange={(e) => setDescription(e.target.value)}
|
236
|
+
onChange={(e) => { setDescription(e.target.value); }}
|
237
237
|
error={Boolean(errors.description)}
|
238
238
|
helperText={
|
239
239
|
errors.description ||
|
@@ -245,7 +245,7 @@ const CreateClubPage = () => {
|
|
245
245
|
label='URL'
|
246
246
|
helperText='Add this if you want to link to an external site'
|
247
247
|
value={externalUrl}
|
248
|
-
onChange={(e) => setExternalUrl(e.target.value)}
|
248
|
+
onChange={(e) => { setExternalUrl(e.target.value); }}
|
249
249
|
/>
|
250
250
|
|
251
251
|
<Grid2 container columnSpacing={2} rowSpacing={3}>
|
@@ -254,7 +254,7 @@ const CreateClubPage = () => {
|
|
254
254
|
label='City'
|
255
255
|
fullWidth
|
256
256
|
value={city}
|
257
|
-
onChange={(e) => setCity(e.target.value)}
|
257
|
+
onChange={(e) => { setCity(e.target.value); }}
|
258
258
|
/>
|
259
259
|
</Grid2>
|
260
260
|
<Grid2 sm={4}>
|
@@ -262,7 +262,7 @@ const CreateClubPage = () => {
|
|
262
262
|
label='State'
|
263
263
|
fullWidth
|
264
264
|
value={state}
|
265
|
-
onChange={(e) => setState(e.target.value)}
|
265
|
+
onChange={(e) => { setState(e.target.value); }}
|
266
266
|
/>
|
267
267
|
</Grid2>
|
268
268
|
<Grid2 sm={4}>
|
@@ -270,7 +270,7 @@ const CreateClubPage = () => {
|
|
270
270
|
label='Country'
|
271
271
|
fullWidth
|
272
272
|
value={country}
|
273
|
-
onChange={(e) => setCountry(e.target.value)}
|
273
|
+
onChange={(e) => { setCountry(e.target.value); }}
|
274
274
|
/>
|
275
275
|
</Grid2>
|
276
276
|
</Grid2>
|
@@ -280,7 +280,7 @@ const CreateClubPage = () => {
|
|
280
280
|
control={
|
281
281
|
<Checkbox
|
282
282
|
checked={!allowFreeTier}
|
283
|
-
onChange={(_, checked) => setAllowFreeTier(!checked)}
|
283
|
+
onChange={(_, checked) => { setAllowFreeTier(!checked); }}
|
284
284
|
/>
|
285
285
|
}
|
286
286
|
label='Limit access to subscribers? If checked, free-tier users will not be able to join.'
|
@@ -289,7 +289,7 @@ const CreateClubPage = () => {
|
|
289
289
|
control={
|
290
290
|
<Checkbox
|
291
291
|
checked={unlisted}
|
292
|
-
onChange={(_, checked) => setUnlisted(checked)}
|
292
|
+
onChange={(_, checked) => { setUnlisted(checked); }}
|
293
293
|
/>
|
294
294
|
}
|
295
295
|
label='Unlisted? If checked, this club will not appear in the list and can only be shared by its URL.'
|
@@ -298,7 +298,7 @@ const CreateClubPage = () => {
|
|
298
298
|
control={
|
299
299
|
<Checkbox
|
300
300
|
checked={approvalRequired}
|
301
|
-
onChange={(_, checked) => setApprovalRequired(checked)}
|
301
|
+
onChange={(_, checked) => { setApprovalRequired(checked); }}
|
302
302
|
/>
|
303
303
|
}
|
304
304
|
label="Require approval to join? If checked, you must manually approve each user's request to join."
|
@@ -159,7 +159,7 @@ const JoinRequest: React.FC<JoinRequestProps> = ({
|
|
159
159
|
<Check
|
160
160
|
color='success'
|
161
161
|
onClick={() =>
|
162
|
-
handleRequest(ClubJoinRequestStatus.Approved)
|
162
|
+
{ handleRequest(ClubJoinRequestStatus.Approved); }
|
163
163
|
}
|
164
164
|
/>
|
165
165
|
</IconButton>
|
@@ -171,9 +171,9 @@ const JoinRequest: React.FC<JoinRequestProps> = ({
|
|
171
171
|
<Block
|
172
172
|
color='error'
|
173
173
|
onClick={() =>
|
174
|
-
handleRequest(
|
174
|
+
{ handleRequest(
|
175
175
|
ClubJoinRequestStatus.Rejected
|
176
|
-
)
|
176
|
+
); }
|
177
177
|
}
|
178
178
|
/>
|
179
179
|
</IconButton>
|
@@ -44,7 +44,7 @@ const ListClubsPage = () => {
|
|
44
44
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
45
45
|
<Tabs
|
46
46
|
value={searchParams.get('view') || 'all'}
|
47
|
-
onChange={(_, t) => setSearchParams({ view: t })}
|
47
|
+
onChange={(_, t) => { setSearchParams({ view: t }); }}
|
48
48
|
variant='scrollable'
|
49
49
|
>
|
50
50
|
<Tab label='All Clubs' value='all' />
|
@@ -62,7 +62,7 @@ const ListClubsPage = () => {
|
|
62
62
|
|
63
63
|
<UpsellDialog
|
64
64
|
open={Boolean(upsellAction)}
|
65
|
-
onClose={() => setUpsellAction('')}
|
65
|
+
onClose={() => { setUpsellAction(''); }}
|
66
66
|
currentAction={upsellAction}
|
67
67
|
/>
|
68
68
|
</Container>
|
@@ -81,10 +81,10 @@ interface CourseEditorErrors {
|
|
81
81
|
};
|
82
82
|
}
|
83
83
|
|
84
|
-
|
84
|
+
interface CourseEditorPageParams {
|
85
85
|
type: CourseType;
|
86
86
|
id: string;
|
87
|
-
}
|
87
|
+
}
|
88
88
|
|
89
89
|
const CourseEditorPage = () => {
|
90
90
|
const params = useParams<CourseEditorPageParams>();
|
@@ -341,7 +341,7 @@ const CourseEditorPage = () => {
|
|
341
341
|
return (
|
342
342
|
<PurchaseCoursePreview
|
343
343
|
course={course}
|
344
|
-
closePreview={() => setShowPreview(false)}
|
344
|
+
closePreview={() => { setShowPreview(false); }}
|
345
345
|
/>
|
346
346
|
);
|
347
347
|
}
|
@@ -437,7 +437,7 @@ const CourseEditorPage = () => {
|
|
437
437
|
<Button
|
438
438
|
size='small'
|
439
439
|
variant='contained'
|
440
|
-
onClick={() => setShowPreview(true)}
|
440
|
+
onClick={() => { setShowPreview(true); }}
|
441
441
|
>
|
442
442
|
Show Preview
|
443
443
|
</Button>
|
@@ -459,7 +459,7 @@ const CourseEditorPage = () => {
|
|
459
459
|
select
|
460
460
|
label='Course Type'
|
461
461
|
value={type}
|
462
|
-
onChange={(e) => setType(e.target.value as CourseType)}
|
462
|
+
onChange={(e) => { setType(e.target.value as CourseType); }}
|
463
463
|
helperText='Changing course type is not implemented yet'
|
464
464
|
>
|
465
465
|
{Object.values(CourseType).map((t) => (
|
@@ -472,7 +472,7 @@ const CourseEditorPage = () => {
|
|
472
472
|
<TextField
|
473
473
|
label='Name'
|
474
474
|
value={name}
|
475
|
-
onChange={(e) => setName(e.target.value)}
|
475
|
+
onChange={(e) => { setName(e.target.value); }}
|
476
476
|
error={Boolean(errors.name)}
|
477
477
|
helperText={errors.name}
|
478
478
|
/>
|
@@ -480,7 +480,7 @@ const CourseEditorPage = () => {
|
|
480
480
|
<TextField
|
481
481
|
label='Author'
|
482
482
|
value={ownerDisplayName}
|
483
|
-
onChange={(e) => setOwnerDisplayName(e.target.value)}
|
483
|
+
onChange={(e) => { setOwnerDisplayName(e.target.value); }}
|
484
484
|
helperText='Defaults to your account display name if left blank'
|
485
485
|
/>
|
486
486
|
|
@@ -490,7 +490,7 @@ const CourseEditorPage = () => {
|
|
490
490
|
minRows={3}
|
491
491
|
maxRows={8}
|
492
492
|
value={description}
|
493
|
-
onChange={(e) => setDescription(e.target.value)}
|
493
|
+
onChange={(e) => { setDescription(e.target.value); }}
|
494
494
|
error={Boolean(errors.description)}
|
495
495
|
helperText={errors.description}
|
496
496
|
/>
|
@@ -520,7 +520,7 @@ const CourseEditorPage = () => {
|
|
520
520
|
label='Bullet Point'
|
521
521
|
value={item}
|
522
522
|
onChange={(e) =>
|
523
|
-
onChangeWhatsIncluded(idx, e.target.value)
|
523
|
+
{ onChangeWhatsIncluded(idx, e.target.value); }
|
524
524
|
}
|
525
525
|
error={Boolean(errors.whatsIncluded?.[idx])}
|
526
526
|
helperText={errors.whatsIncluded?.[idx]}
|
@@ -529,7 +529,7 @@ const CourseEditorPage = () => {
|
|
529
529
|
<span>
|
530
530
|
<IconButton
|
531
531
|
disabled={idx === 0}
|
532
|
-
onClick={() => onMoveUpWhatsIncluded(idx)}
|
532
|
+
onClick={() => { onMoveUpWhatsIncluded(idx); }}
|
533
533
|
>
|
534
534
|
<ArrowUpward />
|
535
535
|
</IconButton>
|
@@ -537,7 +537,7 @@ const CourseEditorPage = () => {
|
|
537
537
|
</Tooltip>
|
538
538
|
<Tooltip title='Delete'>
|
539
539
|
<IconButton
|
540
|
-
onClick={() => onDeleteWhatsIncluded(idx)}
|
540
|
+
onClick={() => { onDeleteWhatsIncluded(idx); }}
|
541
541
|
>
|
542
542
|
<Delete />
|
543
543
|
</IconButton>
|
@@ -546,7 +546,7 @@ const CourseEditorPage = () => {
|
|
546
546
|
))}
|
547
547
|
<Button
|
548
548
|
sx={{ alignSelf: 'start' }}
|
549
|
-
onClick={() => setWhatsIncluded([...whatsIncluded, ''])}
|
549
|
+
onClick={() => { setWhatsIncluded([...whatsIncluded, '']); }}
|
550
550
|
>
|
551
551
|
Add Bullet Point
|
552
552
|
</Button>
|
@@ -559,7 +559,7 @@ const CourseEditorPage = () => {
|
|
559
559
|
<RadioGroup
|
560
560
|
row
|
561
561
|
value={color}
|
562
|
-
onChange={(e) => setColor(e.target.value)}
|
562
|
+
onChange={(e) => { setColor(e.target.value); }}
|
563
563
|
>
|
564
564
|
<FormControlLabel label='None' value='None' control={<Radio />} />
|
565
565
|
<FormControlLabel
|
@@ -588,7 +588,7 @@ const CourseEditorPage = () => {
|
|
588
588
|
fullWidth
|
589
589
|
label='Min Cohort'
|
590
590
|
value={minCohort}
|
591
|
-
onChange={(e) => setMinCohort(e.target.value)}
|
591
|
+
onChange={(e) => { setMinCohort(e.target.value); }}
|
592
592
|
>
|
593
593
|
{dojoCohorts.map((cohort) => (
|
594
594
|
<MenuItem key={cohort} value={cohort}>
|
@@ -613,7 +613,7 @@ const CourseEditorPage = () => {
|
|
613
613
|
fullWidth
|
614
614
|
label='Max Cohort'
|
615
615
|
value={maxCohort}
|
616
|
-
onChange={(e) => setMaxCohort(e.target.value)}
|
616
|
+
onChange={(e) => { setMaxCohort(e.target.value); }}
|
617
617
|
>
|
618
618
|
{dojoCohorts.map((cohort, i) => (
|
619
619
|
<MenuItem
|
@@ -634,7 +634,7 @@ const CourseEditorPage = () => {
|
|
634
634
|
fullWidth
|
635
635
|
label='Cohort Range Description'
|
636
636
|
value={cohortRangeStr}
|
637
|
-
onChange={(e) => setCohortRangeStr(e.target.value)}
|
637
|
+
onChange={(e) => { setCohortRangeStr(e.target.value); }}
|
638
638
|
helperText={`Defaults to ${cohortRange} if left blank`}
|
639
639
|
/>
|
640
640
|
</Grid2>
|
@@ -648,7 +648,7 @@ const CourseEditorPage = () => {
|
|
648
648
|
<Checkbox
|
649
649
|
checked={includedWithSubscription}
|
650
650
|
onChange={(e) =>
|
651
|
-
setIncludedWithSubscription(e.target.checked)
|
651
|
+
{ setIncludedWithSubscription(e.target.checked); }
|
652
652
|
}
|
653
653
|
/>
|
654
654
|
}
|
@@ -659,7 +659,7 @@ const CourseEditorPage = () => {
|
|
659
659
|
<Checkbox
|
660
660
|
checked={availableForFreeUsers}
|
661
661
|
onChange={(e) =>
|
662
|
-
setAvailableForFreeUsers(e.target.checked)
|
662
|
+
{ setAvailableForFreeUsers(e.target.checked); }
|
663
663
|
}
|
664
664
|
/>
|
665
665
|
}
|
@@ -698,7 +698,7 @@ const CourseEditorPage = () => {
|
|
698
698
|
<IconButton
|
699
699
|
disabled={idx === 0}
|
700
700
|
onClick={() =>
|
701
|
-
onMoveUpPurchaseOption(idx)
|
701
|
+
{ onMoveUpPurchaseOption(idx); }
|
702
702
|
}
|
703
703
|
>
|
704
704
|
<ArrowUpward />
|
@@ -707,7 +707,7 @@ const CourseEditorPage = () => {
|
|
707
707
|
</Tooltip>
|
708
708
|
<Tooltip title='Delete'>
|
709
709
|
<IconButton
|
710
|
-
onClick={() => onDeletePurchaseOption(idx)}
|
710
|
+
onClick={() => { onDeletePurchaseOption(idx); }}
|
711
711
|
>
|
712
712
|
<Delete />
|
713
713
|
</IconButton>
|
@@ -719,11 +719,11 @@ const CourseEditorPage = () => {
|
|
719
719
|
label='Name'
|
720
720
|
value={option.name}
|
721
721
|
onChange={(e) =>
|
722
|
-
onChangePurchaseOption(
|
722
|
+
{ onChangePurchaseOption(
|
723
723
|
idx,
|
724
724
|
'name',
|
725
725
|
e.target.value,
|
726
|
-
)
|
726
|
+
); }
|
727
727
|
}
|
728
728
|
helperText='If left blank, it defaults to the course name. Generally set this only if you have multiple purchase options.'
|
729
729
|
/>
|
@@ -731,11 +731,11 @@ const CourseEditorPage = () => {
|
|
731
731
|
label='Full Price'
|
732
732
|
value={option.fullPrice}
|
733
733
|
onChange={(e) =>
|
734
|
-
onChangePurchaseOption(
|
734
|
+
{ onChangePurchaseOption(
|
735
735
|
idx,
|
736
736
|
'fullPrice',
|
737
737
|
e.target.value,
|
738
|
-
)
|
738
|
+
); }
|
739
739
|
}
|
740
740
|
InputProps={{
|
741
741
|
startAdornment: (
|
@@ -756,11 +756,11 @@ const CourseEditorPage = () => {
|
|
756
756
|
variant='outlined'
|
757
757
|
value={option.currentPrice}
|
758
758
|
onChange={(e) =>
|
759
|
-
onChangePurchaseOption(
|
759
|
+
{ onChangePurchaseOption(
|
760
760
|
idx,
|
761
761
|
'currentPrice',
|
762
762
|
e.target.value,
|
763
|
-
)
|
763
|
+
); }
|
764
764
|
}
|
765
765
|
helperText={
|
766
766
|
errors.purchaseOptions?.[idx]?.currentPrice ||
|
@@ -801,12 +801,12 @@ const CourseEditorPage = () => {
|
|
801
801
|
label='Description'
|
802
802
|
value={item.description}
|
803
803
|
onChange={(e) =>
|
804
|
-
onChangeSellingPoint(
|
804
|
+
{ onChangeSellingPoint(
|
805
805
|
idx,
|
806
806
|
spIdx,
|
807
807
|
'description',
|
808
808
|
e.target.value,
|
809
|
-
)
|
809
|
+
); }
|
810
810
|
}
|
811
811
|
/>
|
812
812
|
<FormControlLabel
|
@@ -815,12 +815,12 @@ const CourseEditorPage = () => {
|
|
815
815
|
<Checkbox
|
816
816
|
checked={item.included}
|
817
817
|
onChange={(e) =>
|
818
|
-
onChangeSellingPoint(
|
818
|
+
{ onChangeSellingPoint(
|
819
819
|
idx,
|
820
820
|
spIdx,
|
821
821
|
'included',
|
822
822
|
e.target.checked,
|
823
|
-
)
|
823
|
+
); }
|
824
824
|
}
|
825
825
|
/>
|
826
826
|
}
|
@@ -830,10 +830,10 @@ const CourseEditorPage = () => {
|
|
830
830
|
<IconButton
|
831
831
|
disabled={spIdx === 0}
|
832
832
|
onClick={() =>
|
833
|
-
onMoveUpSellingPoint(
|
833
|
+
{ onMoveUpSellingPoint(
|
834
834
|
idx,
|
835
835
|
spIdx,
|
836
|
-
)
|
836
|
+
); }
|
837
837
|
}
|
838
838
|
>
|
839
839
|
<ArrowUpward />
|
@@ -843,10 +843,10 @@ const CourseEditorPage = () => {
|
|
843
843
|
<Tooltip title='Delete'>
|
844
844
|
<IconButton
|
845
845
|
onClick={() =>
|
846
|
-
onDeleteSellingPoint(
|
846
|
+
{ onDeleteSellingPoint(
|
847
847
|
idx,
|
848
848
|
spIdx,
|
849
|
-
)
|
849
|
+
); }
|
850
850
|
}
|
851
851
|
>
|
852
852
|
<Delete />
|
@@ -856,7 +856,7 @@ const CourseEditorPage = () => {
|
|
856
856
|
))}
|
857
857
|
<Button
|
858
858
|
sx={{ alignSelf: 'start' }}
|
859
|
-
onClick={() => onAddSellingPoint(idx)}
|
859
|
+
onClick={() => { onAddSellingPoint(idx); }}
|
860
860
|
>
|
861
861
|
Add Selling Point
|
862
862
|
</Button>
|
@@ -45,7 +45,7 @@ const PurchaseCoursePreview: React.FC<PurchaseCoursePreviewProps> = ({
|
|
45
45
|
control={
|
46
46
|
<Checkbox
|
47
47
|
checked={isFreeTier}
|
48
|
-
onChange={(e) => setIsFreeTier(e.target.checked)}
|
48
|
+
onChange={(e) => { setIsFreeTier(e.target.checked); }}
|
49
49
|
/>
|
50
50
|
}
|
51
51
|
label='Preview as free-tier user'
|
@@ -55,7 +55,7 @@ const PurchaseCoursePreview: React.FC<PurchaseCoursePreviewProps> = ({
|
|
55
55
|
<TabContext value={previewTab}>
|
56
56
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
57
57
|
<TabList
|
58
|
-
onChange={(_, value) => setPreviewTab(value)}
|
58
|
+
onChange={(_, value) => { setPreviewTab(value); }}
|
59
59
|
aria-label='lab API tabs example'
|
60
60
|
>
|
61
61
|
<Tab label='Course List Page' value='listCourses' />
|
@@ -80,7 +80,7 @@ const CoachListItem: React.FC<{ coach: User }> = ({ coach }) => {
|
|
80
80
|
event.preventDefault();
|
81
81
|
event.stopPropagation();
|
82
82
|
|
83
|
-
if (!currentUser || currentUser
|
83
|
+
if (!currentUser || currentUser.username === coach.username) {
|
84
84
|
return;
|
85
85
|
}
|
86
86
|
|
@@ -104,7 +104,7 @@ const CoachListItem: React.FC<{ coach: User }> = ({ coach }) => {
|
|
104
104
|
|
105
105
|
return (
|
106
106
|
<Card key={coach.username}>
|
107
|
-
<CardActionArea onClick={() => navigate(`/profile/${coach.username}`)}>
|
107
|
+
<CardActionArea onClick={() => { navigate(`/profile/${coach.username}`); }}>
|
108
108
|
<CardContent>
|
109
109
|
<Stack spacing={4}>
|
110
110
|
<Stack
|
@@ -136,7 +136,7 @@ const CoachingListItem: React.FC<{ event: Event }> = ({ event }) => {
|
|
136
136
|
isOwner || isParticipant ? (
|
137
137
|
<Button
|
138
138
|
variant='contained'
|
139
|
-
onClick={() => navigate(`/meeting/${event.id}`)}
|
139
|
+
onClick={() => { navigate(`/meeting/${event.id}`); }}
|
140
140
|
>
|
141
141
|
View Details
|
142
142
|
</Button>
|
@@ -14,7 +14,7 @@ const CoachingPage = () => {
|
|
14
14
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
15
15
|
<Tabs
|
16
16
|
value={searchParams.get('view') || 'coaches'}
|
17
|
-
onChange={(_, t) => setSearchParams({ view: t })}
|
17
|
+
onChange={(_, t) => { setSearchParams({ view: t }); }}
|
18
18
|
variant='scrollable'
|
19
19
|
>
|
20
20
|
<Tab label='Coaches' value='coaches' />
|
@@ -111,10 +111,10 @@ export const CourseFilterEditor: React.FC<CourseFilterEditorProps> = ({ filters
|
|
111
111
|
<Checkbox
|
112
112
|
checked={filters.categories[category]}
|
113
113
|
onChange={(event) =>
|
114
|
-
onChangeCategories(
|
114
|
+
{ onChangeCategories(
|
115
115
|
category,
|
116
116
|
event.target.checked,
|
117
|
-
)
|
117
|
+
); }
|
118
118
|
}
|
119
119
|
color={getCategoryColor(category)}
|
120
120
|
/>
|
@@ -142,7 +142,7 @@ export const CourseFilterEditor: React.FC<CourseFilterEditorProps> = ({ filters
|
|
142
142
|
fullWidth
|
143
143
|
label='Min Cohort'
|
144
144
|
value={filters.minCohort}
|
145
|
-
onChange={(e) => filters.setMinCohort(e.target.value)}
|
145
|
+
onChange={(e) => { filters.setMinCohort(e.target.value); }}
|
146
146
|
>
|
147
147
|
{dojoCohorts.map((cohort) => (
|
148
148
|
<MenuItem key={cohort} value={cohort}>
|
@@ -167,7 +167,7 @@ export const CourseFilterEditor: React.FC<CourseFilterEditorProps> = ({ filters
|
|
167
167
|
fullWidth
|
168
168
|
label='Max Cohort'
|
169
169
|
value={filters.maxCohort}
|
170
|
-
onChange={(e) => filters.setMaxCohort(e.target.value)}
|
170
|
+
onChange={(e) => { filters.setMaxCohort(e.target.value); }}
|
171
171
|
>
|
172
172
|
{dojoCohorts.map((cohort, i) => (
|
173
173
|
<MenuItem
|
@@ -212,7 +212,7 @@ export const CourseFilterEditor: React.FC<CourseFilterEditorProps> = ({ filters
|
|
212
212
|
<Checkbox
|
213
213
|
checked={filters.showAccessible}
|
214
214
|
onChange={(event) =>
|
215
|
-
filters.setShowAccessible(event.target.checked)
|
215
|
+
{ filters.setShowAccessible(event.target.checked); }
|
216
216
|
}
|
217
217
|
/>
|
218
218
|
}
|
@@ -115,7 +115,7 @@ const CourseListItem: React.FC<CourseListItemProps> = ({
|
|
115
115
|
<Link
|
116
116
|
component={RouterLink}
|
117
117
|
to={`/profile/${course.owner}`}
|
118
|
-
onClick={(e) => e.stopPropagation()}
|
118
|
+
onClick={(e) => { e.stopPropagation(); }}
|
119
119
|
>
|
120
120
|
{course.ownerDisplayName}
|
121
121
|
</Link>
|
@@ -20,7 +20,7 @@ export function setCheckoutSessionId(courseId?: string, checkoutId?: string) {
|
|
20
20
|
}
|
21
21
|
|
22
22
|
const courseCheckoutStr = localStorage.getItem(COURSE_STORAGE_KEY);
|
23
|
-
|
23
|
+
const checkoutSessionIds = courseCheckoutStr ? JSON.parse(courseCheckoutStr) : {};
|
24
24
|
checkoutSessionIds[courseId] = checkoutId;
|
25
25
|
localStorage.setItem(COURSE_STORAGE_KEY, JSON.stringify(checkoutSessionIds));
|
26
26
|
}
|
@@ -23,10 +23,10 @@ import PurchaseCoursePage from './PurchaseCoursePage';
|
|
23
23
|
import { AuthStatus, useAuth, useFreeTier } from '../../auth/Auth';
|
24
24
|
import { getCheckoutSessionId, setCheckoutSessionId } from '../localStorage';
|
25
25
|
|
26
|
-
|
26
|
+
interface CoursePageParams {
|
27
27
|
type: CourseType;
|
28
28
|
id: string;
|
29
|
-
}
|
29
|
+
}
|
30
30
|
|
31
31
|
const CoursePage = () => {
|
32
32
|
const navigate = useNavigate();
|
@@ -107,7 +107,7 @@ const CoursePage = () => {
|
|
107
107
|
sx={{ mb: 4 }}
|
108
108
|
action={
|
109
109
|
<Button
|
110
|
-
onClick={() => navigate('/signup')}
|
110
|
+
onClick={() => { navigate('/signup'); }}
|
111
111
|
size='small'
|
112
112
|
color='inherit'
|
113
113
|
>
|
@@ -144,10 +144,10 @@ const CoursePage = () => {
|
|
144
144
|
<Button
|
145
145
|
variant='contained'
|
146
146
|
onClick={() =>
|
147
|
-
setSearchParams({
|
147
|
+
{ setSearchParams({
|
148
148
|
chapter: prevModule.chapterIndex,
|
149
149
|
module: prevModule.moduleIndex,
|
150
|
-
})
|
150
|
+
}); }
|
151
151
|
}
|
152
152
|
>
|
153
153
|
Previous: {prevModule.name}
|
@@ -158,10 +158,10 @@ const CoursePage = () => {
|
|
158
158
|
<Button
|
159
159
|
variant='contained'
|
160
160
|
onClick={() =>
|
161
|
-
setSearchParams({
|
161
|
+
{ setSearchParams({
|
162
162
|
chapter: nextModule.chapterIndex,
|
163
163
|
module: nextModule.moduleIndex,
|
164
|
-
})
|
164
|
+
}); }
|
165
165
|
}
|
166
166
|
>
|
167
167
|
Next: {nextModule.name}
|
@@ -14,10 +14,7 @@ import { useApi } from '../../api/Api';
|
|
14
14
|
function getCompleted(user: User | undefined, module: CourseModule): boolean[] {
|
15
15
|
let exercises: boolean[] = [];
|
16
16
|
if (
|
17
|
-
user
|
18
|
-
user.openingProgress &&
|
19
|
-
user.openingProgress[module.id] &&
|
20
|
-
user.openingProgress[module.id].exercises
|
17
|
+
user?.openingProgress?.[module.id]?.exercises
|
21
18
|
) {
|
22
19
|
exercises = user.openingProgress[module.id].exercises!;
|
23
20
|
}
|
@@ -23,7 +23,7 @@ function getPgnName(header: Record<string, string> | PgnHeaders): string {
|
|
23
23
|
|
24
24
|
interface PgnSelectorProps {
|
25
25
|
pgns?: string[];
|
26
|
-
headers?:
|
26
|
+
headers?: (Record<string, string> | PgnHeaders)[];
|
27
27
|
selectedIndex: number;
|
28
28
|
setSelectedIndex: (i: number) => void;
|
29
29
|
completed?: boolean[];
|
@@ -42,7 +42,7 @@ const PgnSelector: React.FC<PgnSelectorProps> = ({
|
|
42
42
|
hiddenCount,
|
43
43
|
noCard,
|
44
44
|
}) => {
|
45
|
-
let selectedHeaders:
|
45
|
+
let selectedHeaders: (Record<string, string> | PgnHeaders)[] = [];
|
46
46
|
if (headers) {
|
47
47
|
selectedHeaders = headers;
|
48
48
|
} else if (pgns) {
|
@@ -57,7 +57,7 @@ const PgnSelector: React.FC<PgnSelectorProps> = ({
|
|
57
57
|
<ListItemButton
|
58
58
|
sx={{ pl: 0 }}
|
59
59
|
selected={selectedIndex === idx}
|
60
|
-
onClick={() => setSelectedIndex(idx)}
|
60
|
+
onClick={() => { setSelectedIndex(idx); }}
|
61
61
|
>
|
62
62
|
<ListItemIcon sx={{ minWidth: '40px' }}>
|
63
63
|
<Stack alignItems='center' width={1}>
|
@@ -44,14 +44,10 @@ export interface Club {
|
|
44
44
|
|
45
45
|
export interface ClubDetails extends Club {
|
46
46
|
/** The members of the club, mapped by their usernames. */
|
47
|
-
members:
|
48
|
-
[username: string]: ClubMember;
|
49
|
-
};
|
47
|
+
members: Record<string, ClubMember>;
|
50
48
|
|
51
49
|
/** The pending requests to join the club, mapped by their usernames. */
|
52
|
-
joinRequests:
|
53
|
-
[username: string]: ClubJoinRequest;
|
54
|
-
};
|
50
|
+
joinRequests: Record<string, ClubJoinRequest>;
|
55
51
|
}
|
56
52
|
|
57
53
|
export interface ClubLocation {
|
@@ -11,9 +11,7 @@ export interface Graduation {
|
|
11
11
|
startRating: number;
|
12
12
|
currentRating: number;
|
13
13
|
comments: string;
|
14
|
-
progress:
|
15
|
-
[id: string]: RequirementProgress;
|
16
|
-
};
|
14
|
+
progress: Record<string, RequirementProgress>;
|
17
15
|
graduationCohorts: string[];
|
18
16
|
startedAt: string;
|
19
17
|
createdAt: string;
|
@@ -52,9 +52,7 @@ export interface Notification {
|
|
52
52
|
id: string;
|
53
53
|
|
54
54
|
/** The headers of the Game. */
|
55
|
-
headers:
|
56
|
-
[key: string]: string;
|
57
|
-
};
|
55
|
+
headers: Record<string, string>;
|
58
56
|
};
|
59
57
|
|
60
58
|
/** Metadata for a game review Notification. */
|
@@ -66,9 +64,7 @@ export interface Notification {
|
|
66
64
|
id: string;
|
67
65
|
|
68
66
|
/** The headers of the Game. */
|
69
|
-
headers:
|
70
|
-
[key: string]: string;
|
71
|
-
};
|
67
|
+
headers: Record<string, string>;
|
72
68
|
|
73
69
|
/** The reviewer of the Game. */
|
74
70
|
reviewer: {
|
@@ -122,9 +118,7 @@ export interface Notification {
|
|
122
118
|
result: string;
|
123
119
|
|
124
120
|
/** The headers of the game. */
|
125
|
-
headers:
|
126
|
-
[key: string]: string;
|
127
|
-
};
|
121
|
+
headers: Record<string, string>;
|
128
122
|
};
|
129
123
|
|
130
124
|
/** Metadata for a club join request notification. */
|
@@ -140,16 +134,16 @@ export interface Notification {
|
|
140
134
|
export function getTitle(notification: Notification): string {
|
141
135
|
switch (notification.type) {
|
142
136
|
case NotificationType.GameComment:
|
143
|
-
return `${notification.gameCommentMetadata?.headers
|
137
|
+
return `${notification.gameCommentMetadata?.headers.White} - ${notification.gameCommentMetadata?.headers.Black}`;
|
144
138
|
case NotificationType.GameReviewComplete:
|
145
|
-
return `${notification.gameReviewMetadata?.headers
|
139
|
+
return `${notification.gameReviewMetadata?.headers.White} - ${notification.gameReviewMetadata?.headers.Black}`;
|
146
140
|
case NotificationType.NewFollower:
|
147
141
|
return 'You have a new follower';
|
148
142
|
case NotificationType.TimelineComment:
|
149
143
|
case NotificationType.TimelineReaction:
|
150
144
|
return `${notification.timelineCommentMetadata?.name}`;
|
151
145
|
case NotificationType.ExplorerGame:
|
152
|
-
return `${notification.explorerGameMetadata?.headers
|
146
|
+
return `${notification.explorerGameMetadata?.headers.White} - ${notification.explorerGameMetadata?.headers.Black}`;
|
153
147
|
case NotificationType.NewClubJoinRequest:
|
154
148
|
return `${notification.clubMetadata?.name}`;
|
155
149
|
case NotificationType.ClubJoinRequestApproved:
|
@@ -158,7 +152,7 @@ export function getTitle(notification: Notification): string {
|
|
158
152
|
}
|
159
153
|
|
160
154
|
export function getDescription(notification: Notification): string {
|
161
|
-
|
155
|
+
const count = notification.count || 1;
|
162
156
|
|
163
157
|
switch (notification.type) {
|
164
158
|
case NotificationType.GameComment:
|
@@ -19,9 +19,7 @@ export interface CustomTask {
|
|
19
19
|
owner: string;
|
20
20
|
name: string;
|
21
21
|
description: string;
|
22
|
-
counts:
|
23
|
-
[cohort: string]: number;
|
24
|
-
};
|
22
|
+
counts: Record<string, number>;
|
25
23
|
scoreboardDisplay: ScoreboardDisplay;
|
26
24
|
category: RequirementCategory;
|
27
25
|
updatedAt: string;
|
@@ -54,15 +52,11 @@ export interface Requirement {
|
|
54
52
|
name: string;
|
55
53
|
description: string;
|
56
54
|
freeDescription: string;
|
57
|
-
counts:
|
58
|
-
[cohort: string]: number;
|
59
|
-
};
|
55
|
+
counts: Record<string, number>;
|
60
56
|
startCount: number;
|
61
57
|
numberOfCohorts: number;
|
62
58
|
unitScore: number;
|
63
|
-
unitScoreOverride?:
|
64
|
-
[cohort: string]: number;
|
65
|
-
};
|
59
|
+
unitScoreOverride?: Record<string, number>;
|
66
60
|
totalScore: number;
|
67
61
|
videoUrls?: string[];
|
68
62
|
positions?: Position[];
|
@@ -82,12 +76,8 @@ export interface Requirement {
|
|
82
76
|
|
83
77
|
export interface RequirementProgress {
|
84
78
|
requirementId: string;
|
85
|
-
counts:
|
86
|
-
|
87
|
-
};
|
88
|
-
minutesSpent: {
|
89
|
-
[cohort: string]: number;
|
90
|
-
};
|
79
|
+
counts: Record<string, number>;
|
80
|
+
minutesSpent: Record<string, number>;
|
91
81
|
updatedAt: string;
|
92
82
|
}
|
93
83
|
|
@@ -244,7 +234,7 @@ export function getTotalScore(cohort: string | undefined, requirements: Requirem
|
|
244
234
|
return sum + r.totalScore;
|
245
235
|
}
|
246
236
|
let unitScore = r.unitScore;
|
247
|
-
if (r.unitScoreOverride
|
237
|
+
if (r.unitScoreOverride?.[cohort] !== undefined) {
|
248
238
|
unitScore = r.unitScoreOverride[cohort];
|
249
239
|
}
|
250
240
|
const count = r.counts[cohort] || 0;
|
@@ -306,8 +296,7 @@ export function getTotalCategoryScore(
|
|
306
296
|
|
307
297
|
export function getUnitScore(cohort: string, requirement: Requirement): number {
|
308
298
|
if (
|
309
|
-
requirement.unitScoreOverride
|
310
|
-
requirement.unitScoreOverride[cohort] !== undefined
|
299
|
+
requirement.unitScoreOverride?.[cohort] !== undefined
|
311
300
|
) {
|
312
301
|
return requirement.unitScoreOverride[cohort];
|
313
302
|
}
|
@@ -4,8 +4,7 @@ import { User } from './user';
|
|
4
4
|
* Represents a single row of data in the summary scoreboards
|
5
5
|
* (IE: full dojo and follower scoreboards).
|
6
6
|
*/
|
7
|
-
export
|
8
|
-
extends Pick<
|
7
|
+
export type ScoreboardSummary = Pick<
|
9
8
|
User,
|
10
9
|
| 'username'
|
11
10
|
| 'displayName'
|
@@ -16,7 +15,7 @@ export interface ScoreboardSummary
|
|
16
15
|
| 'dojoCohort'
|
17
16
|
| 'totalDojoScore'
|
18
17
|
| 'minutesSpent'
|
19
|
-
>
|
18
|
+
>
|
20
19
|
|
21
20
|
/**
|
22
21
|
* Returns true if the provided object is a ScoreboardSummary.
|
@@ -13,12 +13,8 @@ export interface CohortStatistics {
|
|
13
13
|
activeRatingChanges: number;
|
14
14
|
inactiveRatingChanges: number;
|
15
15
|
|
16
|
-
activeRatingSystems:
|
17
|
-
|
18
|
-
};
|
19
|
-
inactiveRatingSystems: {
|
20
|
-
[system: string]: number;
|
21
|
-
};
|
16
|
+
activeRatingSystems: Record<string, number>;
|
17
|
+
inactiveRatingSystems: Record<string, number>;
|
22
18
|
|
23
19
|
activeMinutesSpent: number;
|
24
20
|
inactiveMinutesSpent: number;
|
@@ -33,7 +29,5 @@ export interface CohortStatistics {
|
|
33
29
|
}
|
34
30
|
|
35
31
|
export interface UserStatistics {
|
36
|
-
cohorts:
|
37
|
-
[cohort: string]: CohortStatistics;
|
38
|
-
};
|
32
|
+
cohorts: Record<string, CohortStatistics>;
|
39
33
|
}
|
@@ -101,7 +101,7 @@ export interface User {
|
|
101
101
|
ratings: Partial<Record<RatingSystem, Rating>>;
|
102
102
|
ratingHistories?: Record<RatingSystem, RatingHistory[]>;
|
103
103
|
|
104
|
-
progress:
|
104
|
+
progress: Record<string, RequirementProgress>;
|
105
105
|
disableBookingNotifications: boolean;
|
106
106
|
disableCancellationNotifications: boolean;
|
107
107
|
isAdmin: boolean;
|
@@ -124,11 +124,9 @@ export interface User {
|
|
124
124
|
|
125
125
|
customTasks?: CustomTask[];
|
126
126
|
|
127
|
-
openingProgress?: {
|
128
|
-
[moduleId: string]: {
|
127
|
+
openingProgress?: Record<string, {
|
129
128
|
exercises?: boolean[];
|
130
|
-
}
|
131
|
-
};
|
129
|
+
}>;
|
132
130
|
|
133
131
|
tutorials?: Record<string, boolean>;
|
134
132
|
minutesSpent?: Record<MinutesSpentKey, number>;
|
@@ -707,7 +705,7 @@ export function normalizeToFide(rating: number, ratingSystem: RatingSystem): num
|
|
707
705
|
}
|
708
706
|
|
709
707
|
export function shouldPromptGraduation(user?: User): boolean {
|
710
|
-
if (!user
|
708
|
+
if (!user?.dojoCohort || !user.ratingSystem) {
|
711
709
|
return false;
|
712
710
|
}
|
713
711
|
if (user.ratingSystem === RatingSystem.Custom) {
|
@@ -735,7 +733,7 @@ const THREE_MONTHS = 1000 * 60 * 60 * 24 * 90;
|
|
735
733
|
* @returns True if the user should be prompted to demote.
|
736
734
|
*/
|
737
735
|
export function shouldPromptDemotion(user?: User): boolean {
|
738
|
-
if (!user
|
736
|
+
if (!user?.dojoCohort || !user.ratingSystem) {
|
739
737
|
return false;
|
740
738
|
}
|
741
739
|
if (user.ratingSystem === RatingSystem.Custom) {
|
@@ -62,7 +62,7 @@ const UnsubscribePage = () => {
|
|
62
62
|
<TextField
|
63
63
|
label='Email'
|
64
64
|
value={email}
|
65
|
-
onChange={(e) => setEmail(e.target.value)}
|
65
|
+
onChange={(e) => { setEmail(e.target.value); }}
|
66
66
|
/>
|
67
67
|
|
68
68
|
<LoadingButton
|
@@ -120,7 +120,7 @@ const EditGamePage = () => {
|
|
120
120
|
{preflight && (
|
121
121
|
<SubmitGamePreflight
|
122
122
|
open={true}
|
123
|
-
onClose={() => setPreflight(undefined)}
|
123
|
+
onClose={() => { setPreflight(undefined); }}
|
124
124
|
initHeaders={preflight.headers}
|
125
125
|
loading={request.isLoading()}
|
126
126
|
onSubmit={onPreflight}
|
@@ -93,7 +93,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
93
93
|
if (black.trim() === '') {
|
94
94
|
errors.black = 'This field is required';
|
95
95
|
}
|
96
|
-
if (!date
|
96
|
+
if (!date?.isValid) {
|
97
97
|
errors.date = 'This field is required';
|
98
98
|
}
|
99
99
|
if (result.trim() === '') {
|
@@ -132,7 +132,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
132
132
|
<FormControl>
|
133
133
|
<RadioGroup
|
134
134
|
value={type}
|
135
|
-
onChange={(e, v) => setType(v as GameSubmissionType)}
|
135
|
+
onChange={(e, v) => { setType(v as GameSubmissionType); }}
|
136
136
|
>
|
137
137
|
<FormControlLabel
|
138
138
|
value={GameSubmissionType.LichessChapter}
|
@@ -165,7 +165,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
165
165
|
label='Lichess Chapter URL'
|
166
166
|
placeholder='https://lichess.org/study/abcd1234/abcd1234'
|
167
167
|
value={lichessUrl}
|
168
|
-
onChange={(e) => setLichessUrl(e.target.value)}
|
168
|
+
onChange={(e) => { setLichessUrl(e.target.value); }}
|
169
169
|
error={!!errors.lichessUrl}
|
170
170
|
helperText={
|
171
171
|
errors.lichessUrl ||
|
@@ -180,7 +180,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
180
180
|
label='Lichess Study URL'
|
181
181
|
placeholder='https://lichess.org/study/abcd1234'
|
182
182
|
value={lichessUrl}
|
183
|
-
onChange={(e) => setLichessUrl(e.target.value)}
|
183
|
+
onChange={(e) => { setLichessUrl(e.target.value); }}
|
184
184
|
error={!!errors.lichessUrl}
|
185
185
|
helperText={
|
186
186
|
errors.lichessUrl ||
|
@@ -195,7 +195,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
195
195
|
label='PGN Text'
|
196
196
|
placeholder={pgnTextPlaceholder}
|
197
197
|
value={pgnText}
|
198
|
-
onChange={(e) => setPgnText(e.target.value)}
|
198
|
+
onChange={(e) => { setPgnText(e.target.value); }}
|
199
199
|
multiline
|
200
200
|
minRows={5}
|
201
201
|
error={!!errors.pgnText}
|
@@ -215,7 +215,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
215
215
|
data-cy={`white`}
|
216
216
|
label='White'
|
217
217
|
value={white}
|
218
|
-
onChange={(e) => setWhite(e.target.value)}
|
218
|
+
onChange={(e) => { setWhite(e.target.value); }}
|
219
219
|
error={!!errors.white}
|
220
220
|
helperText={errors.white}
|
221
221
|
/>
|
@@ -225,7 +225,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
225
225
|
data-cy={`black`}
|
226
226
|
label='Black'
|
227
227
|
value={black}
|
228
|
-
onChange={(e) => setBlack(e.target.value)}
|
228
|
+
onChange={(e) => { setBlack(e.target.value); }}
|
229
229
|
error={!!errors.black}
|
230
230
|
helperText={errors.black}
|
231
231
|
/>
|
@@ -236,7 +236,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
236
236
|
data-cy={`result`}
|
237
237
|
label='Result'
|
238
238
|
value={result}
|
239
|
-
onChange={(e) => setResult(e.target.value)}
|
239
|
+
onChange={(e) => { setResult(e.target.value); }}
|
240
240
|
error={!!errors.result}
|
241
241
|
helperText={errors.result}
|
242
242
|
>
|
@@ -249,7 +249,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
249
249
|
label='Date'
|
250
250
|
disableFuture
|
251
251
|
value={date}
|
252
|
-
onChange={(newValue) => setDate(newValue)}
|
252
|
+
onChange={(newValue) => { setDate(newValue); }}
|
253
253
|
slotProps={{
|
254
254
|
textField: {
|
255
255
|
id: `date`,
|
@@ -271,7 +271,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
271
271
|
? 'unlisted'
|
272
272
|
: visibility
|
273
273
|
}
|
274
|
-
onChange={(e) => setVisibility(e.target.value)}
|
274
|
+
onChange={(e) => { setVisibility(e.target.value); }}
|
275
275
|
>
|
276
276
|
<FormControlLabel
|
277
277
|
value='public'
|
@@ -315,7 +315,7 @@ const GameSubmissionForm: React.FC<GameSubmissionFormProps> = ({
|
|
315
315
|
<RadioGroup
|
316
316
|
row
|
317
317
|
value={orientation}
|
318
|
-
onChange={(e, v) => setOrientation(v)}
|
318
|
+
onChange={(e, v) => { setOrientation(v); }}
|
319
319
|
>
|
320
320
|
<FormControlLabel
|
321
321
|
value='white'
|
@@ -96,7 +96,7 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
96
96
|
};
|
97
97
|
|
98
98
|
const submit = () => {
|
99
|
-
|
99
|
+
const errors: Record<number, FormError> = {};
|
100
100
|
headers.forEach((h, i) => {
|
101
101
|
const error: FormError = { white: '', black: '', result: '', date: '' };
|
102
102
|
if (h.white.trim() === '') {
|
@@ -112,7 +112,7 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
112
112
|
errors[i] = error;
|
113
113
|
}
|
114
114
|
console.log('h.date: ', h.date);
|
115
|
-
if (
|
115
|
+
if (!h.date?.isValid) {
|
116
116
|
error.date = 'This field is required';
|
117
117
|
errors[i] = error;
|
118
118
|
}
|
@@ -160,10 +160,10 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
160
160
|
label='White'
|
161
161
|
value={h.white}
|
162
162
|
onChange={(e) =>
|
163
|
-
onChangeHeader(i, 'white', e.target.value)
|
163
|
+
{ onChangeHeader(i, 'white', e.target.value); }
|
164
164
|
}
|
165
|
-
error={!!errors[i]
|
166
|
-
helperText={errors[i]
|
165
|
+
error={!!errors[i].white}
|
166
|
+
helperText={errors[i].white}
|
167
167
|
/>
|
168
168
|
</Grid2>
|
169
169
|
|
@@ -174,10 +174,10 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
174
174
|
label='Black'
|
175
175
|
value={h.black}
|
176
176
|
onChange={(e) =>
|
177
|
-
onChangeHeader(i, 'black', e.target.value)
|
177
|
+
{ onChangeHeader(i, 'black', e.target.value); }
|
178
178
|
}
|
179
|
-
error={!!errors[i]
|
180
|
-
helperText={errors[i]
|
179
|
+
error={!!errors[i].black}
|
180
|
+
helperText={errors[i].black}
|
181
181
|
/>
|
182
182
|
</Grid2>
|
183
183
|
|
@@ -188,10 +188,10 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
188
188
|
label='Result'
|
189
189
|
value={h.result}
|
190
190
|
onChange={(e) =>
|
191
|
-
onChangeHeader(i, 'result', e.target.value)
|
191
|
+
{ onChangeHeader(i, 'result', e.target.value); }
|
192
192
|
}
|
193
|
-
error={!!errors[i]
|
194
|
-
helperText={errors[i]
|
193
|
+
error={!!errors[i].result}
|
194
|
+
helperText={errors[i].result}
|
195
195
|
fullWidth
|
196
196
|
>
|
197
197
|
<MenuItem value='1-0'>White Won</MenuItem>
|
@@ -211,8 +211,8 @@ const SubmitGamePreflight: React.FC<SubmitGamePreflightProps> = ({
|
|
211
211
|
slotProps={{
|
212
212
|
textField: {
|
213
213
|
id: `date-${i}`,
|
214
|
-
error: !!errors[i]
|
215
|
-
helperText: errors[i]
|
214
|
+
error: !!errors[i].date,
|
215
|
+
helperText: errors[i].date,
|
216
216
|
fullWidth: true,
|
217
217
|
},
|
218
218
|
}}
|
@@ -45,7 +45,7 @@ export const gameTableColumns: GridColDef<GameInfo>[] = [
|
|
45
45
|
direction='row'
|
46
46
|
spacing={1}
|
47
47
|
alignItems='center'
|
48
|
-
onClick={(e) => e.stopPropagation()}
|
48
|
+
onClick={(e) => { e.stopPropagation(); }}
|
49
49
|
>
|
50
50
|
<CohortIcon cohort={params.value} size={25} tooltip='' />
|
51
51
|
<Typography variant='body2'>{params.value}</Typography>
|
@@ -67,7 +67,7 @@ export const gameTableColumns: GridColDef<GameInfo>[] = [
|
|
67
67
|
direction='row'
|
68
68
|
spacing={1}
|
69
69
|
alignItems='center'
|
70
|
-
onClick={(e) => e.stopPropagation()}
|
70
|
+
onClick={(e) => { e.stopPropagation(); }}
|
71
71
|
>
|
72
72
|
<Avatar
|
73
73
|
username={params.row.owner}
|
@@ -269,8 +269,8 @@ const ListGamesPage = () => {
|
|
269
269
|
pageSize={pageSize}
|
270
270
|
count={rowCount}
|
271
271
|
hasMore={hasMore}
|
272
|
-
onPrevPage={() => setPage(page - 1)}
|
273
|
-
onNextPage={() => setPage(page + 1)}
|
272
|
+
onPrevPage={() => { setPage(page - 1); }}
|
273
|
+
onNextPage={() => { setPage(page + 1); }}
|
274
274
|
/>
|
275
275
|
),
|
276
276
|
}}
|
@@ -99,7 +99,7 @@ export const SearchByCohort: React.FC<SearchByCohortProps> = ({
|
|
99
99
|
data-cy='cohort-select'
|
100
100
|
value={cohort}
|
101
101
|
label='Cohort'
|
102
|
-
onChange={(e) => setCohort(e.target.value)}
|
102
|
+
onChange={(e) => { setCohort(e.target.value); }}
|
103
103
|
>
|
104
104
|
{dojoCohorts.map((c) => (
|
105
105
|
<MenuItem key={c} value={c}>
|
@@ -259,7 +259,7 @@ const SearchByPlayer: React.FC<SearchByPlayerProps> = ({
|
|
259
259
|
data-cy='player-name'
|
260
260
|
label='Player Name'
|
261
261
|
value={player}
|
262
|
-
onChange={(e) => setPlayer(e.target.value)}
|
262
|
+
onChange={(e) => { setPlayer(e.target.value); }}
|
263
263
|
error={!!errors.player}
|
264
264
|
helperText={errors.player}
|
265
265
|
/>
|
@@ -268,7 +268,7 @@ const SearchByPlayer: React.FC<SearchByPlayerProps> = ({
|
|
268
268
|
data-cy='color'
|
269
269
|
value={color}
|
270
270
|
label='Color'
|
271
|
-
onChange={(e) => setColor(e.target.value)}
|
271
|
+
onChange={(e) => { setColor(e.target.value); }}
|
272
272
|
>
|
273
273
|
<MenuItem value='either'>Either</MenuItem>
|
274
274
|
<MenuItem value='white'>White</MenuItem>
|
@@ -367,7 +367,7 @@ const SearchByOpening: React.FC<SearchByOpeningProps> = ({
|
|
367
367
|
data-cy='opening-eco'
|
368
368
|
value={eco}
|
369
369
|
label='Opening ECO'
|
370
|
-
onChange={(e) => setEco(e.target.value)}
|
370
|
+
onChange={(e) => { setEco(e.target.value); }}
|
371
371
|
error={!!errors.eco}
|
372
372
|
helperText={errors.eco}
|
373
373
|
/>
|
@@ -449,7 +449,7 @@ const SearchByPosition: React.FC<SearchByPositionProps> = ({
|
|
449
449
|
data-cy='fen'
|
450
450
|
value={fen}
|
451
451
|
label='FEN'
|
452
|
-
onChange={(e) => setFen(e.target.value)}
|
452
|
+
onChange={(e) => { setFen(e.target.value); }}
|
453
453
|
error={!!errors.fen}
|
454
454
|
helperText={errors.fen}
|
455
455
|
/>
|
@@ -555,7 +555,7 @@ const SearchFilters: React.FC<SearchFiltersProps> = ({ isLoading, onSearch }) =>
|
|
555
555
|
let endDateStr: string | undefined = undefined;
|
556
556
|
if (isValid(new Date(paramsStartDate || ''))) {
|
557
557
|
startDateStr = new Date(paramsStartDate || '')
|
558
|
-
|
558
|
+
.toISOString()
|
559
559
|
.substring(0, 10)
|
560
560
|
.replaceAll('-', '.');
|
561
561
|
}
|
@@ -569,7 +569,7 @@ const SearchFilters: React.FC<SearchFiltersProps> = ({ isLoading, onSearch }) =>
|
|
569
569
|
// Functions that actually perform the search
|
570
570
|
const searchByCohort = useCallback(
|
571
571
|
(startKey: string) =>
|
572
|
-
api.listGamesByCohort(cohort
|
572
|
+
api.listGamesByCohort(cohort, startKey, startDateStr, endDateStr),
|
573
573
|
[cohort, api, startDateStr, endDateStr],
|
574
574
|
);
|
575
575
|
|
@@ -580,8 +580,8 @@ const SearchFilters: React.FC<SearchFiltersProps> = ({ isLoading, onSearch }) =>
|
|
580
580
|
startKey,
|
581
581
|
startDateStr,
|
582
582
|
endDateStr,
|
583
|
-
player
|
584
|
-
color
|
583
|
+
player,
|
584
|
+
color,
|
585
585
|
),
|
586
586
|
[api, startDateStr, endDateStr, player, color],
|
587
587
|
);
|
@@ -8,7 +8,7 @@ import { useSearchParams } from 'react-router-dom';
|
|
8
8
|
|
9
9
|
export type SearchFunc = (
|
10
10
|
startKey: string
|
11
|
-
) => Promise<AxiosResponse<ListGamesResponse
|
11
|
+
) => Promise<AxiosResponse<ListGamesResponse>>;
|
12
12
|
|
13
13
|
export function usePagination(
|
14
14
|
initialSearchFunc: SearchFunc | null,
|
@@ -109,7 +109,7 @@ export function usePagination(
|
|
109
109
|
});
|
110
110
|
}, [page, pageSize, games, startKey, searchFunc, request]);
|
111
111
|
|
112
|
-
|
112
|
+
const rowCount = games.length;
|
113
113
|
// if (startKey !== undefined) {
|
114
114
|
// rowCount += pageSize;
|
115
115
|
// }
|
@@ -42,7 +42,7 @@ const columns: GridColDef<GameInfo>[] = [
|
|
42
42
|
direction='row'
|
43
43
|
spacing={1}
|
44
44
|
alignItems='center'
|
45
|
-
onClick={(e) => e.stopPropagation()}
|
45
|
+
onClick={(e) => { e.stopPropagation(); }}
|
46
46
|
>
|
47
47
|
<Avatar
|
48
48
|
username={params.row.owner}
|
@@ -101,7 +101,7 @@ const columns: GridColDef<GameInfo>[] = [
|
|
101
101
|
align: 'right',
|
102
102
|
headerAlign: 'right',
|
103
103
|
valueFormatter: (params: GridValueFormatterParams<string>) => {
|
104
|
-
return params.value
|
104
|
+
return params.value.split('T')[0].replaceAll('-', '.');
|
105
105
|
},
|
106
106
|
},
|
107
107
|
{
|
@@ -213,8 +213,8 @@ const ReviewQueuePage = () => {
|
|
213
213
|
pageSize={pageSize}
|
214
214
|
count={rowCount}
|
215
215
|
hasMore={hasMore}
|
216
|
-
onPrevPage={() => setPage(page - 1)}
|
217
|
-
onNextPage={() => setPage(page + 1)}
|
216
|
+
onPrevPage={() => { setPage(page - 1); }}
|
217
|
+
onNextPage={() => { setPage(page + 1); }}
|
218
218
|
/>
|
219
219
|
),
|
220
220
|
}}
|
@@ -58,7 +58,7 @@ const DeleteGameButton: React.FC<DeleteGameButtonProps> = ({
|
|
58
58
|
<Tooltip title='Delete Game'>
|
59
59
|
<IconButton
|
60
60
|
data-cy='delete-game-button'
|
61
|
-
onClick={() => setShowDeleteModal(true)}
|
61
|
+
onClick={() => { setShowDeleteModal(true); }}
|
62
62
|
>
|
63
63
|
<DeleteIcon sx={{ color: 'text.secondary' }} />
|
64
64
|
</IconButton>
|
@@ -67,7 +67,7 @@ const DeleteGameButton: React.FC<DeleteGameButtonProps> = ({
|
|
67
67
|
<Button
|
68
68
|
data-cy='delete-game-button'
|
69
69
|
variant={variant}
|
70
|
-
onClick={() => setShowDeleteModal(true)}
|
70
|
+
onClick={() => { setShowDeleteModal(true); }}
|
71
71
|
color='error'
|
72
72
|
>
|
73
73
|
Delete Game
|
@@ -9,11 +9,11 @@ import { DefaultUnderboardTab } from '../../board/pgn/boardTools/underboard/Unde
|
|
9
9
|
import { Game } from '../../database/game';
|
10
10
|
import PgnErrorBoundary from './PgnErrorBoundary';
|
11
11
|
|
12
|
-
|
12
|
+
interface GameContextType {
|
13
13
|
game?: Game;
|
14
14
|
onUpdateGame?: (g: Game) => void;
|
15
15
|
isOwner?: boolean;
|
16
|
-
}
|
16
|
+
}
|
17
17
|
|
18
18
|
const GameContext = createContext<GameContextType>({});
|
19
19
|
|
@@ -27,8 +27,7 @@ interface ErrorBoundaryState {
|
|
27
27
|
|
28
28
|
class PgnErrorBoundary extends Component<
|
29
29
|
React.PropsWithChildren<PgnErrorBoundaryNavigatorProps>,
|
30
|
-
ErrorBoundaryState
|
31
|
-
any
|
30
|
+
ErrorBoundaryState
|
32
31
|
> {
|
33
32
|
constructor(props: PgnErrorBoundaryNavigatorProps) {
|
34
33
|
super(props);
|
@@ -71,7 +70,7 @@ class PgnErrorBoundary extends Component<
|
|
71
70
|
<Stack direction='row' spacing={2}>
|
72
71
|
<Button
|
73
72
|
variant='contained'
|
74
|
-
onClick={() => this.props.navigate('./edit')}
|
73
|
+
onClick={() => { this.props.navigate('./edit'); }}
|
75
74
|
>
|
76
75
|
Resubmit PGN
|
77
76
|
</Button>
|
@@ -384,7 +384,7 @@ const AuthenticatedHelp = () => {
|
|
384
384
|
<Link
|
385
385
|
key={section.title}
|
386
386
|
href={`#${section.title}`}
|
387
|
-
onClick={(e) => scrollToId(e, section.title)}
|
387
|
+
onClick={(e) => { scrollToId(e, section.title); }}
|
388
388
|
>
|
389
389
|
{section.title}
|
390
390
|
</Link>
|
@@ -394,7 +394,7 @@ const AuthenticatedHelp = () => {
|
|
394
394
|
<Link
|
395
395
|
href={`#${item.title}`}
|
396
396
|
onClick={(e) =>
|
397
|
-
scrollToId(e, item.title)
|
397
|
+
{ scrollToId(e, item.title); }
|
398
398
|
}
|
399
399
|
>
|
400
400
|
{item.title}
|
@@ -406,7 +406,7 @@ const AuthenticatedHelp = () => {
|
|
406
406
|
))}
|
407
407
|
<Link
|
408
408
|
href='#support-ticket'
|
409
|
-
onClick={(e) => scrollToId(e, 'support-ticket')}
|
409
|
+
onClick={(e) => { scrollToId(e, 'support-ticket'); }}
|
410
410
|
>
|
411
411
|
Open a Support Ticket
|
412
412
|
</Link>
|
@@ -447,10 +447,10 @@ const AuthenticatedHelp = () => {
|
|
447
447
|
<li>
|
448
448
|
<Button
|
449
449
|
onClick={() =>
|
450
|
-
onTutorial(
|
450
|
+
{ onTutorial(
|
451
451
|
'/profile',
|
452
452
|
TutorialName.ProfilePage,
|
453
|
-
)
|
453
|
+
); }
|
454
454
|
}
|
455
455
|
sx={{ textTransform: 'none' }}
|
456
456
|
>
|
@@ -460,10 +460,10 @@ const AuthenticatedHelp = () => {
|
|
460
460
|
<li>
|
461
461
|
<Button
|
462
462
|
onClick={() =>
|
463
|
-
onTutorial(
|
463
|
+
{ onTutorial(
|
464
464
|
'/scoreboard',
|
465
465
|
TutorialName.ScoreboardPage,
|
466
|
-
)
|
466
|
+
); }
|
467
467
|
}
|
468
468
|
sx={{ textTransform: 'none' }}
|
469
469
|
>
|
@@ -473,10 +473,10 @@ const AuthenticatedHelp = () => {
|
|
473
473
|
<li>
|
474
474
|
<Button
|
475
475
|
onClick={() =>
|
476
|
-
onTutorial(
|
476
|
+
{ onTutorial(
|
477
477
|
'/calendar',
|
478
478
|
TutorialName.CalendarPage,
|
479
|
-
)
|
479
|
+
); }
|
480
480
|
}
|
481
481
|
sx={{ textTransform: 'none' }}
|
482
482
|
>
|
@@ -486,10 +486,10 @@ const AuthenticatedHelp = () => {
|
|
486
486
|
<li>
|
487
487
|
<Button
|
488
488
|
onClick={() =>
|
489
|
-
onTutorial(
|
489
|
+
{ onTutorial(
|
490
490
|
'/games',
|
491
491
|
TutorialName.ListGamesPage,
|
492
|
-
)
|
492
|
+
); }
|
493
493
|
}
|
494
494
|
sx={{ textTransform: 'none' }}
|
495
495
|
>
|
@@ -83,7 +83,7 @@ const SupportTicket = () => {
|
|
83
83
|
data-cy='support-ticket-name'
|
84
84
|
label='Full Name'
|
85
85
|
value={name}
|
86
|
-
onChange={(e) => setName(e.target.value)}
|
86
|
+
onChange={(e) => { setName(e.target.value); }}
|
87
87
|
error={Boolean(errors.name)}
|
88
88
|
helperText={errors.name}
|
89
89
|
fullWidth
|
@@ -95,7 +95,7 @@ const SupportTicket = () => {
|
|
95
95
|
data-cy='support-ticket-email'
|
96
96
|
label='Email'
|
97
97
|
value={email}
|
98
|
-
onChange={(e) => setEmail(e.target.value)}
|
98
|
+
onChange={(e) => { setEmail(e.target.value); }}
|
99
99
|
error={Boolean(errors.email)}
|
100
100
|
helperText={errors.email}
|
101
101
|
fullWidth
|
@@ -107,7 +107,7 @@ const SupportTicket = () => {
|
|
107
107
|
data-cy='support-ticket-subject'
|
108
108
|
label='Subject'
|
109
109
|
value={subject}
|
110
|
-
onChange={(e) => setSubject(e.target.value)}
|
110
|
+
onChange={(e) => { setSubject(e.target.value); }}
|
111
111
|
error={Boolean(errors.subject)}
|
112
112
|
helperText={errors.subject}
|
113
113
|
fullWidth
|
@@ -119,7 +119,7 @@ const SupportTicket = () => {
|
|
119
119
|
data-cy='support-ticket-message'
|
120
120
|
label='Message'
|
121
121
|
value={message}
|
122
|
-
onChange={(e) => setMessage(e.target.value)}
|
122
|
+
onChange={(e) => { setMessage(e.target.value); }}
|
123
123
|
error={Boolean(errors.message)}
|
124
124
|
helperText={errors.message}
|
125
125
|
fullWidth
|
@@ -97,7 +97,7 @@ const UnauthenticatedHelp = () => {
|
|
97
97
|
<React.Fragment key={section.title}>
|
98
98
|
<Link
|
99
99
|
href={`#${section.title}`}
|
100
|
-
onClick={(e) => scrollToId(e, section.title)}
|
100
|
+
onClick={(e) => { scrollToId(e, section.title); }}
|
101
101
|
>
|
102
102
|
{section.title}
|
103
103
|
</Link>
|
@@ -107,7 +107,7 @@ const UnauthenticatedHelp = () => {
|
|
107
107
|
<Link
|
108
108
|
href={`#${item.title}`}
|
109
109
|
onClick={(e) =>
|
110
|
-
scrollToId(e, item.title)
|
110
|
+
{ scrollToId(e, item.title); }
|
111
111
|
}
|
112
112
|
>
|
113
113
|
{item.title}
|
@@ -119,7 +119,7 @@ const UnauthenticatedHelp = () => {
|
|
119
119
|
))}
|
120
120
|
<Link
|
121
121
|
href='#support-ticket'
|
122
|
-
onClick={(e) => scrollToId(e, 'support-ticket')}
|
122
|
+
onClick={(e) => { scrollToId(e, 'support-ticket'); }}
|
123
123
|
>
|
124
124
|
Open a Support Ticket
|
125
125
|
</Link>
|
@@ -8,7 +8,7 @@ import { ReportCallback } from 'web-vitals';
|
|
8
8
|
|
9
9
|
ReactGA.initialize('G-9VPNTDELD2');
|
10
10
|
|
11
|
-
const root = ReactDOM.createRoot(document.getElementById('root')
|
11
|
+
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
12
12
|
root.render(
|
13
13
|
<React.StrictMode>
|
14
14
|
<App />
|
@@ -29,8 +29,8 @@ const JoinToday = () => {
|
|
29
29
|
|
30
30
|
<Grid2 container spacing={3} width={1}>
|
31
31
|
<PriceMatrix
|
32
|
-
onSubscribe={() => navigate('/signup', { state: locationState })}
|
33
|
-
onFreeTier={() => navigate('/signup', { state: locationState })}
|
32
|
+
onSubscribe={() => { navigate('/signup', { state: locationState }); }}
|
33
|
+
onFreeTier={() => { navigate('/signup', { state: locationState }); }}
|
34
34
|
/>
|
35
35
|
</Grid2>
|
36
36
|
</Stack>
|
@@ -72,7 +72,7 @@ const LandingPage = () => {
|
|
72
72
|
<Button
|
73
73
|
variant='contained'
|
74
74
|
onClick={() =>
|
75
|
-
navigate('/signup', { state: locationState })
|
75
|
+
{ navigate('/signup', { state: locationState }); }
|
76
76
|
}
|
77
77
|
sx={{
|
78
78
|
fontSize: '1rem',
|
@@ -87,7 +87,7 @@ const LandingPage = () => {
|
|
87
87
|
<Button
|
88
88
|
variant='outlined'
|
89
89
|
onClick={() =>
|
90
|
-
navigate('/signin', { state: locationState })
|
90
|
+
{ navigate('/signin', { state: locationState }); }
|
91
91
|
}
|
92
92
|
sx={{
|
93
93
|
fontSize: '1rem',
|
@@ -134,7 +134,7 @@ const WhatsIncluded = () => {
|
|
134
134
|
<Tabs
|
135
135
|
data-cy='whatsincluded-tab-list'
|
136
136
|
value={value}
|
137
|
-
onChange={(_, t) => setValue(t)}
|
137
|
+
onChange={(_, t) => { setValue(t); }}
|
138
138
|
variant='scrollable'
|
139
139
|
sx={{ justifyContent: 'center' }}
|
140
140
|
>
|
@@ -107,7 +107,7 @@ const MemorizeGamesPage = () => {
|
|
107
107
|
|
108
108
|
<FormControl>
|
109
109
|
<FormLabel>Mode</FormLabel>
|
110
|
-
<RadioGroup row value={mode} onChange={(e) => setMode(e.target.value)}>
|
110
|
+
<RadioGroup row value={mode} onChange={(e) => { setMode(e.target.value); }}>
|
111
111
|
<FormControlLabel value='study' control={<Radio />} label='Study' />
|
112
112
|
<FormControlLabel value='test' control={<Radio />} label='Test' />
|
113
113
|
</RadioGroup>
|
@@ -105,7 +105,7 @@ const ModelGamesPage = () => {
|
|
105
105
|
label='Cohort'
|
106
106
|
value={cohort}
|
107
107
|
onChange={(event) =>
|
108
|
-
onChangeCohort(event.target.value)
|
108
|
+
{ onChangeCohort(event.target.value); }
|
109
109
|
}
|
110
110
|
sx={{ mb: 3 }}
|
111
111
|
fullWidth
|
@@ -54,7 +54,7 @@ const CancelMeetingButton: React.FC<
|
|
54
54
|
<Button
|
55
55
|
variant='contained'
|
56
56
|
color='error'
|
57
|
-
onClick={() => setShowCancelDialog(true)}
|
57
|
+
onClick={() => { setShowCancelDialog(true); }}
|
58
58
|
>
|
59
59
|
{children}
|
60
60
|
</Button>
|
@@ -63,7 +63,7 @@ const CancelMeetingButton: React.FC<
|
|
63
63
|
onClose={
|
64
64
|
cancelRequest.isLoading()
|
65
65
|
? undefined
|
66
|
-
: () => setShowCancelDialog(false)
|
66
|
+
: () => { setShowCancelDialog(false); }
|
67
67
|
}
|
68
68
|
>
|
69
69
|
<RequestSnackbar request={cancelRequest} />
|
@@ -71,7 +71,7 @@ const CancelMeetingButton: React.FC<
|
|
71
71
|
{dialogTitle}
|
72
72
|
<IconButton
|
73
73
|
aria-label='close'
|
74
|
-
onClick={() => setShowCancelDialog(false)}
|
74
|
+
onClick={() => { setShowCancelDialog(false); }}
|
75
75
|
sx={{
|
76
76
|
position: 'absolute',
|
77
77
|
right: 10,
|
@@ -50,7 +50,7 @@ const ListMeetingsPage = () => {
|
|
50
50
|
Looks like you don't have any meetings. Go to the calendar and
|
51
51
|
schedule one now!
|
52
52
|
</Typography>
|
53
|
-
<Button variant='contained' onClick={() => navigate('/calendar')}>
|
53
|
+
<Button variant='contained' onClick={() => { navigate('/calendar'); }}>
|
54
54
|
Go to Calendar
|
55
55
|
</Button>
|
56
56
|
</>
|
@@ -21,7 +21,7 @@ const MeetingMessages = () => {
|
|
21
21
|
const messages = meeting?.messages;
|
22
22
|
|
23
23
|
useEffect(() => {
|
24
|
-
bottomRef.current?.scrollTo(0, bottomRef.current
|
24
|
+
bottomRef.current?.scrollTo(0, bottomRef.current.scrollHeight || 0);
|
25
25
|
}, [messages]);
|
26
26
|
|
27
27
|
if (!meeting) {
|
@@ -34,7 +34,7 @@ const MeetingMessages = () => {
|
|
34
34
|
if (
|
35
35
|
meeting.type === EventType.Coaching &&
|
36
36
|
meeting.owner !== user.username &&
|
37
|
-
!meeting.participants[user.username]
|
37
|
+
!meeting.participants[user.username].hasPaid
|
38
38
|
) {
|
39
39
|
return null;
|
40
40
|
}
|
@@ -132,7 +132,7 @@ const MeetingPage = () => {
|
|
132
132
|
<Container maxWidth='md' sx={{ py: 4 }}>
|
133
133
|
<Typography>This meeting has not been booked yet.</Typography>
|
134
134
|
<Button
|
135
|
-
onClick={() => navigate('/calendar')}
|
135
|
+
onClick={() => { navigate('/calendar'); }}
|
136
136
|
variant='contained'
|
137
137
|
sx={{ mt: 2 }}
|
138
138
|
>
|
@@ -71,7 +71,7 @@ export const Logo = () => {
|
|
71
71
|
cursor: 'pointer',
|
72
72
|
}}
|
73
73
|
alt=''
|
74
|
-
onClick={() => navigate('/')}
|
74
|
+
onClick={() => { navigate('/'); }}
|
75
75
|
/>
|
76
76
|
);
|
77
77
|
};
|
@@ -96,118 +96,118 @@ function allStartItems(
|
|
96
96
|
{
|
97
97
|
name: 'Newsfeed',
|
98
98
|
icon: <Feed />,
|
99
|
-
onClick: () => navigate('/newsfeed'),
|
99
|
+
onClick: () => { navigate('/newsfeed'); },
|
100
100
|
},
|
101
101
|
{
|
102
102
|
name: 'Training Plan',
|
103
103
|
icon: <Checklist />,
|
104
|
-
onClick: () => navigate('/profile?view=progress'),
|
104
|
+
onClick: () => { navigate('/profile?view=progress'); },
|
105
105
|
},
|
106
106
|
{
|
107
107
|
name: 'Scoreboard',
|
108
108
|
icon: <Scoreboard />,
|
109
|
-
onClick: () => toggleExpansion('Scoreboard'),
|
109
|
+
onClick: () => { toggleExpansion('Scoreboard'); },
|
110
110
|
children: [
|
111
111
|
{
|
112
112
|
name: 'My Cohort',
|
113
113
|
icon: <GroupIcon />,
|
114
|
-
onClick: () => navigate('/scoreboard'),
|
114
|
+
onClick: () => { navigate('/scoreboard'); },
|
115
115
|
},
|
116
116
|
{
|
117
117
|
name: 'Full Dojo',
|
118
118
|
icon: <LanguageIcon />,
|
119
|
-
onClick: () => navigate('scoreboard/dojo'),
|
119
|
+
onClick: () => { navigate('scoreboard/dojo'); },
|
120
120
|
},
|
121
121
|
{
|
122
122
|
name: 'Followers',
|
123
123
|
icon: <ThumbUpIcon />,
|
124
|
-
onClick: () => navigate('scoreboard/following'),
|
124
|
+
onClick: () => { navigate('scoreboard/following'); },
|
125
125
|
},
|
126
126
|
{
|
127
127
|
name: 'Search Users',
|
128
128
|
icon: <SearchIcon />,
|
129
|
-
onClick: () => navigate('scoreboard/search'),
|
129
|
+
onClick: () => { navigate('scoreboard/search'); },
|
130
130
|
},
|
131
131
|
{
|
132
132
|
name: 'Statistics',
|
133
133
|
icon: <AutoGraphIcon />,
|
134
|
-
onClick: () => navigate('scoreboard/stats'),
|
134
|
+
onClick: () => { navigate('scoreboard/stats'); },
|
135
135
|
},
|
136
136
|
],
|
137
137
|
},
|
138
138
|
{
|
139
139
|
name: 'Tournaments',
|
140
140
|
icon: <Tournaments />,
|
141
|
-
onClick: () => toggleExpansion('Tournaments'),
|
141
|
+
onClick: () => { toggleExpansion('Tournaments'); },
|
142
142
|
children: [
|
143
143
|
{
|
144
144
|
name: 'DojoLiga',
|
145
145
|
icon: <MilitaryTech />,
|
146
|
-
onClick: () => navigate('/tournaments'),
|
146
|
+
onClick: () => { navigate('/tournaments'); },
|
147
147
|
},
|
148
148
|
{
|
149
149
|
name: 'Open Classical',
|
150
150
|
icon: <MilitaryTech />,
|
151
|
-
onClick: () => navigate('/tournaments/open-classical'),
|
151
|
+
onClick: () => { navigate('/tournaments/open-classical'); },
|
152
152
|
},
|
153
153
|
],
|
154
154
|
},
|
155
155
|
{
|
156
156
|
name: 'Games',
|
157
157
|
icon: <PawnIcon />,
|
158
|
-
onClick: () => navigate('/games'),
|
158
|
+
onClick: () => { navigate('/games'); },
|
159
159
|
},
|
160
160
|
{
|
161
161
|
name: 'Calendar',
|
162
162
|
icon: <CalendarToday />,
|
163
|
-
onClick: () => navigate('/calendar'),
|
163
|
+
onClick: () => { navigate('/calendar'); },
|
164
164
|
},
|
165
165
|
{
|
166
166
|
name: 'Material',
|
167
167
|
icon: <MenuBook />,
|
168
|
-
onClick: () => toggleExpansion('Material'),
|
168
|
+
onClick: () => { toggleExpansion('Material'); },
|
169
169
|
children: [
|
170
170
|
{
|
171
171
|
name: 'Courses',
|
172
172
|
icon: <ImportContacts />,
|
173
|
-
onClick: () => navigate('/courses'),
|
173
|
+
onClick: () => { navigate('/courses'); },
|
174
174
|
},
|
175
175
|
{
|
176
176
|
name: 'Tactics Tests',
|
177
177
|
icon: <Speed />,
|
178
|
-
onClick: () => navigate('/tactics'),
|
178
|
+
onClick: () => { navigate('/tactics'); },
|
179
179
|
},
|
180
180
|
{
|
181
181
|
name: 'Books',
|
182
182
|
icon: <AutoStories />,
|
183
|
-
onClick: () => navigate('/material/books'),
|
183
|
+
onClick: () => { navigate('/material/books'); },
|
184
184
|
},
|
185
185
|
{
|
186
186
|
name: 'Sparring Positions',
|
187
187
|
icon: <LocalFireDepartment />,
|
188
|
-
onClick: () => navigate('/material/sparring'),
|
188
|
+
onClick: () => { navigate('/material/sparring'); },
|
189
189
|
},
|
190
190
|
{
|
191
191
|
name: 'Model Annotations',
|
192
192
|
icon: <BorderColor />,
|
193
|
-
onClick: () => navigate('/material/modelgames'),
|
193
|
+
onClick: () => { navigate('/material/modelgames'); },
|
194
194
|
},
|
195
195
|
{
|
196
196
|
name: 'Games to Memorize',
|
197
197
|
icon: <Psychology />,
|
198
|
-
onClick: () => navigate('/material/memorizegames'),
|
198
|
+
onClick: () => { navigate('/material/memorizegames'); },
|
199
199
|
},
|
200
200
|
{
|
201
201
|
name: 'Rating Conversions',
|
202
202
|
icon: <SignalCellularAlt />,
|
203
|
-
onClick: () => navigate('/material/ratings'),
|
203
|
+
onClick: () => { navigate('/material/ratings'); },
|
204
204
|
},
|
205
205
|
],
|
206
206
|
},
|
207
207
|
{
|
208
208
|
name: 'Clubs',
|
209
209
|
icon: <Groups />,
|
210
|
-
onClick: () => navigate('/clubs'),
|
210
|
+
onClick: () => { navigate('/clubs'); },
|
211
211
|
},
|
212
212
|
{
|
213
213
|
name: 'Blog',
|
@@ -217,17 +217,17 @@ function allStartItems(
|
|
217
217
|
{
|
218
218
|
name: 'Shop',
|
219
219
|
icon: <Sell />,
|
220
|
-
onClick: () => toggleExpansion('Shop'),
|
220
|
+
onClick: () => { toggleExpansion('Shop'); },
|
221
221
|
children: [
|
222
222
|
{
|
223
223
|
name: 'Courses',
|
224
224
|
icon: <ImportContacts />,
|
225
|
-
onClick: () => navigate('/courses'),
|
225
|
+
onClick: () => { navigate('/courses'); },
|
226
226
|
},
|
227
227
|
{
|
228
228
|
name: 'Coaching',
|
229
229
|
icon: <RocketLaunch />,
|
230
|
-
onClick: () => navigate('/coaching'),
|
230
|
+
onClick: () => { navigate('/coaching'); },
|
231
231
|
},
|
232
232
|
{
|
233
233
|
name: 'Merch',
|
@@ -244,7 +244,7 @@ function helpItem(navigate: NavigateFunction): NavbarItem {
|
|
244
244
|
return {
|
245
245
|
name: 'Help',
|
246
246
|
icon: <Help />,
|
247
|
-
onClick: () => navigate('/help'),
|
247
|
+
onClick: () => { navigate('/help'); },
|
248
248
|
};
|
249
249
|
}
|
250
250
|
|
@@ -257,7 +257,7 @@ function NotificationsMenuItem({
|
|
257
257
|
const navigate = useNavigate();
|
258
258
|
|
259
259
|
return (
|
260
|
-
<MenuItem onClick={handleClick(() => navigate('/notifications'))}>
|
260
|
+
<MenuItem onClick={handleClick(() => { navigate('/notifications'); })}>
|
261
261
|
<ListItemIcon>
|
262
262
|
<Badge
|
263
263
|
badgeContent={notifications.length}
|
@@ -421,7 +421,7 @@ function HelpButton(navigate: NavigateFunction) {
|
|
421
421
|
data-cy='Help'
|
422
422
|
key='help'
|
423
423
|
sx={{ color: 'white' }}
|
424
|
-
onClick={() => navigate('/help')}
|
424
|
+
onClick={() => { navigate('/help'); }}
|
425
425
|
>
|
426
426
|
<Help />
|
427
427
|
</IconButton>
|
@@ -450,7 +450,7 @@ function useNavbarItems(
|
|
450
450
|
const showProfileDropdown = useMediaQuery('(min-width:542px)');
|
451
451
|
|
452
452
|
const startItems = allStartItems(navigate, (item: string) =>
|
453
|
-
setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })),
|
453
|
+
{ setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })); },
|
454
454
|
);
|
455
455
|
|
456
456
|
let startItemCount = 0;
|
@@ -564,7 +564,7 @@ const LargeMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
564
564
|
<Logo />
|
565
565
|
<Stack spacing={1} direction='row' sx={{ flexGrow: 1 }}>
|
566
566
|
<Button
|
567
|
-
onClick={() => navigate('/profile')}
|
567
|
+
onClick={() => { navigate('/profile'); }}
|
568
568
|
sx={{ color: 'white' }}
|
569
569
|
startIcon={<Person2Icon />}
|
570
570
|
>
|
@@ -572,7 +572,7 @@ const LargeMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
572
572
|
</Button>
|
573
573
|
</Stack>
|
574
574
|
|
575
|
-
<Button onClick={() => navigate('/help')} sx={{ color: 'white' }}>
|
575
|
+
<Button onClick={() => { navigate('/help'); }} sx={{ color: 'white' }}>
|
576
576
|
Help
|
577
577
|
</Button>
|
578
578
|
|
@@ -625,7 +625,7 @@ const ExtraSmallMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
625
625
|
const [openItems, setOpenItems] = useState<Record<string, boolean>>({});
|
626
626
|
|
627
627
|
const startItems = allStartItems(navigate, (item: string) =>
|
628
|
-
setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })),
|
628
|
+
{ setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })); },
|
629
629
|
);
|
630
630
|
|
631
631
|
if (auth.status === AuthStatus.Unauthenticated) {
|
@@ -689,7 +689,7 @@ const ExtraSmallMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
689
689
|
onClose={handleClose}
|
690
690
|
>
|
691
691
|
{!profileCreated && (
|
692
|
-
<MenuItem onClick={handleClick(() => navigate('/profile'))}>
|
692
|
+
<MenuItem onClick={handleClick(() => { navigate('/profile'); })}>
|
693
693
|
<ListItemIcon>
|
694
694
|
<Person2Icon />
|
695
695
|
</ListItemIcon>
|
@@ -699,7 +699,7 @@ const ExtraSmallMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
699
699
|
|
700
700
|
{startItemsJsx}
|
701
701
|
|
702
|
-
<MenuItem onClick={handleClick(() => navigate('/notifications'))}>
|
702
|
+
<MenuItem onClick={handleClick(() => { navigate('/notifications'); })}>
|
703
703
|
<ListItemIcon>
|
704
704
|
<Badge
|
705
705
|
badgeContent={notifications.length}
|
@@ -712,7 +712,7 @@ const ExtraSmallMenu: React.FC<MenuProps> = ({ meetingCount }) => {
|
|
712
712
|
<Typography textAlign='center'>Notifications</Typography>
|
713
713
|
</MenuItem>
|
714
714
|
|
715
|
-
<MenuItem onClick={handleClick(() => navigate('/help'))}>
|
715
|
+
<MenuItem onClick={handleClick(() => { navigate('/help'); })}>
|
716
716
|
<ListItemIcon>
|
717
717
|
<Help />
|
718
718
|
</ListItemIcon>
|
@@ -2,9 +2,9 @@ import { faChessPawn } from '@fortawesome/free-solid-svg-icons';
|
|
2
2
|
import { SvgIcon } from '@mui/material';
|
3
3
|
import { forwardRef } from 'react';
|
4
4
|
|
5
|
-
|
5
|
+
interface FontAwesomeSvgIconProps {
|
6
6
|
icon: any;
|
7
|
-
}
|
7
|
+
}
|
8
8
|
|
9
9
|
const FontAwesomeSvgIcon = forwardRef<SVGSVGElement, FontAwesomeSvgIconProps>(
|
10
10
|
(props, ref) => {
|
@@ -79,14 +79,14 @@ const ProfileButton = () => {
|
|
79
79
|
horizontal: 'right',
|
80
80
|
}}
|
81
81
|
>
|
82
|
-
<MenuItem onClick={handleClick(() => navigate('/profile?view=stats'))}>
|
82
|
+
<MenuItem onClick={handleClick(() => { navigate('/profile?view=stats'); })}>
|
83
83
|
<ListItemIcon>
|
84
84
|
<Person2Icon />
|
85
85
|
</ListItemIcon>
|
86
86
|
<Typography textAlign='center'>Profile</Typography>
|
87
87
|
</MenuItem>
|
88
88
|
|
89
|
-
<MenuItem onClick={handleClick(() => navigate('/profile/edit'))}>
|
89
|
+
<MenuItem onClick={handleClick(() => { navigate('/profile/edit'); })}>
|
90
90
|
<ListItemIcon>
|
91
91
|
<SettingsIcon />
|
92
92
|
</ListItemIcon>
|
@@ -94,7 +94,7 @@ const ProfileButton = () => {
|
|
94
94
|
</MenuItem>
|
95
95
|
|
96
96
|
{user.isCoach && (
|
97
|
-
<MenuItem onClick={handleClick(() => navigate('/coach'))}>
|
97
|
+
<MenuItem onClick={handleClick(() => { navigate('/coach'); })}>
|
98
98
|
<ListItemIcon>
|
99
99
|
<SportsIcon />
|
100
100
|
</ListItemIcon>
|
@@ -48,37 +48,37 @@ function unauthenticatedStartItems(
|
|
48
48
|
{
|
49
49
|
name: 'Tournaments',
|
50
50
|
icon: <Tournaments />,
|
51
|
-
onClick: () => toggleExpansion('Tournaments'),
|
51
|
+
onClick: () => { toggleExpansion('Tournaments'); },
|
52
52
|
children: [
|
53
53
|
{
|
54
54
|
name: 'DojoLiga',
|
55
|
-
onClick: () => navigate('/tournaments'),
|
55
|
+
onClick: () => { navigate('/tournaments'); },
|
56
56
|
},
|
57
57
|
{
|
58
58
|
name: 'Open Classical',
|
59
|
-
onClick: () => navigate('/tournaments/open-classical'),
|
59
|
+
onClick: () => { navigate('/tournaments/open-classical'); },
|
60
60
|
},
|
61
61
|
],
|
62
62
|
},
|
63
63
|
{
|
64
64
|
name: 'Material',
|
65
65
|
icon: <MenuBook />,
|
66
|
-
onClick: () => toggleExpansion('Material'),
|
66
|
+
onClick: () => { toggleExpansion('Material'); },
|
67
67
|
children: [
|
68
68
|
{
|
69
69
|
name: 'Courses',
|
70
70
|
icon: <ImportContacts />,
|
71
|
-
onClick: () => navigate('/courses'),
|
71
|
+
onClick: () => { navigate('/courses'); },
|
72
72
|
},
|
73
73
|
{
|
74
74
|
name: 'Books',
|
75
75
|
icon: <AutoStories />,
|
76
|
-
onClick: () => navigate('/material/books'),
|
76
|
+
onClick: () => { navigate('/material/books'); },
|
77
77
|
},
|
78
78
|
{
|
79
79
|
name: 'Rating Conversions',
|
80
80
|
icon: <SignalCellularAlt />,
|
81
|
-
onClick: () => navigate('/material/ratings'),
|
81
|
+
onClick: () => { navigate('/material/ratings'); },
|
82
82
|
},
|
83
83
|
],
|
84
84
|
},
|
@@ -90,15 +90,15 @@ function unauthenticatedStartItems(
|
|
90
90
|
{
|
91
91
|
name: 'Shop',
|
92
92
|
icon: <Sell />,
|
93
|
-
onClick: () => toggleExpansion('Shop'),
|
93
|
+
onClick: () => { toggleExpansion('Shop'); },
|
94
94
|
children: [
|
95
95
|
{
|
96
96
|
name: 'Courses',
|
97
|
-
onClick: () => navigate('/courses'),
|
97
|
+
onClick: () => { navigate('/courses'); },
|
98
98
|
},
|
99
99
|
{
|
100
100
|
name: 'Coaching',
|
101
|
-
onClick: () => navigate('/coaching'),
|
101
|
+
onClick: () => { navigate('/coaching'); },
|
102
102
|
},
|
103
103
|
{
|
104
104
|
name: 'Merch',
|
@@ -110,7 +110,7 @@ function unauthenticatedStartItems(
|
|
110
110
|
{
|
111
111
|
name: 'Contact Us',
|
112
112
|
icon: <ContactSupport />,
|
113
|
-
onClick: () => navigate('/help'),
|
113
|
+
onClick: () => { navigate('/help'); },
|
114
114
|
},
|
115
115
|
];
|
116
116
|
}
|
@@ -125,7 +125,7 @@ function useNavbarItems(handleClick: (func: () => void) => () => void) {
|
|
125
125
|
const hide4 = useMediaQuery('(min-width:600px)');
|
126
126
|
|
127
127
|
const startItems = unauthenticatedStartItems(navigate, (item: string) =>
|
128
|
-
setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })),
|
128
|
+
{ setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })); },
|
129
129
|
);
|
130
130
|
|
131
131
|
let startItemCount = 0;
|
@@ -214,10 +214,10 @@ export const LargeMenuUnauthenticated = () => {
|
|
214
214
|
</Stack>
|
215
215
|
|
216
216
|
<Stack spacing={1} direction='row'>
|
217
|
-
<Button onClick={() => navigate('/signin')} sx={{ color: 'white' }}>
|
217
|
+
<Button onClick={() => { navigate('/signin'); }} sx={{ color: 'white' }}>
|
218
218
|
Sign In
|
219
219
|
</Button>
|
220
|
-
<Button onClick={() => navigate('/signup')} sx={{ color: 'white' }}>
|
220
|
+
<Button onClick={() => { navigate('/signup'); }} sx={{ color: 'white' }}>
|
221
221
|
Sign Up
|
222
222
|
</Button>
|
223
223
|
</Stack>
|
@@ -231,7 +231,7 @@ export const ExtraSmallMenuUnauthenticated = () => {
|
|
231
231
|
const [openItems, setOpenItems] = useState<Record<string, boolean>>({});
|
232
232
|
|
233
233
|
const startItems = unauthenticatedStartItems(navigate, (item: string) =>
|
234
|
-
setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })),
|
234
|
+
{ setOpenItems((v) => ({ ...v, [item]: !(v[item] || false) })); },
|
235
235
|
);
|
236
236
|
|
237
237
|
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
@@ -275,10 +275,10 @@ export const ExtraSmallMenuUnauthenticated = () => {
|
|
275
275
|
open={Boolean(anchorEl)}
|
276
276
|
onClose={handleClose}
|
277
277
|
>
|
278
|
-
<MenuItem onClick={handleClick(() => navigate('/signin'))}>
|
278
|
+
<MenuItem onClick={handleClick(() => { navigate('/signin'); })}>
|
279
279
|
<Typography textAlign='center'>Sign In</Typography>
|
280
280
|
</MenuItem>
|
281
|
-
<MenuItem onClick={handleClick(() => navigate('/signup'))}>
|
281
|
+
<MenuItem onClick={handleClick(() => { navigate('/signup'); })}>
|
282
282
|
<Typography textAlign='center'>Sign Up</Typography>
|
283
283
|
</MenuItem>
|
284
284
|
|
@@ -57,7 +57,7 @@ function CommentEditor<T, CreateFunctionProps>(
|
|
57
57
|
fullWidth
|
58
58
|
multiline
|
59
59
|
value={comment}
|
60
|
-
onChange={(e) => setComment(e.target.value)}
|
60
|
+
onChange={(e) => { setComment(e.target.value); }}
|
61
61
|
/>
|
62
62
|
|
63
63
|
{request.isLoading() ? (
|
@@ -8,10 +8,10 @@ import NewsfeedItem from './NewsfeedItem';
|
|
8
8
|
import LoadingPage from '../../loading/LoadingPage';
|
9
9
|
import NotFoundPage from '../../NotFoundPage';
|
10
10
|
|
11
|
-
|
11
|
+
interface NewsfeedDetailPageParams {
|
12
12
|
owner: string;
|
13
13
|
id: string;
|
14
|
-
}
|
14
|
+
}
|
15
15
|
|
16
16
|
const NewsfeedDetailPage = () => {
|
17
17
|
const { owner, id } = useParams<NewsfeedDetailPageParams>();
|
@@ -202,7 +202,7 @@ const ReactionList: React.FC<ReactionListProps> = ({ owner, id, reactions, onEdi
|
|
202
202
|
variant={
|
203
203
|
isReactor(user, reactions, type) ? 'contained' : 'outlined'
|
204
204
|
}
|
205
|
-
onClick={() => onReact(type)}
|
205
|
+
onClick={() => { onReact(type); }}
|
206
206
|
>
|
207
207
|
<Paper
|
208
208
|
elevation={2}
|
@@ -242,7 +242,7 @@ const ReactionList: React.FC<ReactionListProps> = ({ owner, id, reactions, onEdi
|
|
242
242
|
<IconButton
|
243
243
|
key={type}
|
244
244
|
sx={{ width: '2.96875rem' }}
|
245
|
-
onClick={() => onReact(type)}
|
245
|
+
onClick={() => { onReact(type); }}
|
246
246
|
>
|
247
247
|
<ReactionEmoji type={type} icon />
|
248
248
|
</IconButton>
|
@@ -22,7 +22,7 @@ const MenuProps = {
|
|
22
22
|
function getStyles(value: string, selected: readonly string[], theme: Theme) {
|
23
23
|
return {
|
24
24
|
fontWeight:
|
25
|
-
selected.
|
25
|
+
!selected.includes(value)
|
26
26
|
? theme.typography.fontWeightRegular
|
27
27
|
: theme.typography.fontWeightMedium,
|
28
28
|
};
|
@@ -68,7 +68,7 @@ function useNewsfeedIds(initialNewsfeedIds: string[]): [string[], (v: string[])
|
|
68
68
|
setNewsfeedIds(newValue);
|
69
69
|
|
70
70
|
const removedIds = initialNewsfeedIds.filter(
|
71
|
-
(id) => newValue.
|
71
|
+
(id) => !newValue.includes(id),
|
72
72
|
);
|
73
73
|
|
74
74
|
for (const id of removedIds) {
|
@@ -222,7 +222,7 @@ const NewsfeedList: React.FC<NewsfeedListProps> = ({
|
|
222
222
|
let shownEntries = data?.entries ?? [];
|
223
223
|
if (showAdditionalFilters) {
|
224
224
|
shownEntries = shownEntries.filter((entry) =>
|
225
|
-
filters.some((filterKey) => Filters[filterKey]
|
225
|
+
filters.some((filterKey) => Filters[filterKey](entry)),
|
226
226
|
);
|
227
227
|
}
|
228
228
|
|
@@ -138,8 +138,8 @@ const GamesTab: React.FC<GamesTabProps> = ({ user }) => {
|
|
138
138
|
pageSize={pageSize}
|
139
139
|
count={rowCount}
|
140
140
|
hasMore={hasMore}
|
141
|
-
onPrevPage={() => setPage(page - 1)}
|
142
|
-
onNextPage={() => setPage(page + 1)}
|
141
|
+
onPrevPage={() => { setPage(page - 1); }}
|
142
|
+
onNextPage={() => { setPage(page + 1); }}
|
143
143
|
/>
|
144
144
|
),
|
145
145
|
}}
|
@@ -87,7 +87,7 @@ const GraduationDialog = () => {
|
|
87
87
|
<Dialog
|
88
88
|
open={showGraduationDialog}
|
89
89
|
onClose={
|
90
|
-
request.isLoading() ? undefined : () => setShowGraduationDialog(false)
|
90
|
+
request.isLoading() ? undefined : () => { setShowGraduationDialog(false); }
|
91
91
|
}
|
92
92
|
fullWidth
|
93
93
|
>
|
@@ -109,7 +109,7 @@ const GraduationDialog = () => {
|
|
109
109
|
<TextField
|
110
110
|
label='Comments'
|
111
111
|
value={comments}
|
112
|
-
onChange={(event) => setComments(event.target.value)}
|
112
|
+
onChange={(event) => { setComments(event.target.value); }}
|
113
113
|
multiline
|
114
114
|
minRows={3}
|
115
115
|
maxRows={3}
|
@@ -119,7 +119,7 @@ const GraduationDialog = () => {
|
|
119
119
|
</DialogContent>
|
120
120
|
<DialogActions>
|
121
121
|
<Button
|
122
|
-
onClick={() => setShowGraduationDialog(false)}
|
122
|
+
onClick={() => { setShowGraduationDialog(false); }}
|
123
123
|
disabled={request.isLoading()}
|
124
124
|
>
|
125
125
|
Cancel
|
@@ -37,9 +37,9 @@ import ProgressTab from './progress/ProgressTab';
|
|
37
37
|
import StatsTab from './stats/StatsTab';
|
38
38
|
import ProfilePageTutorial from './tutorials/ProfilePageTutorial';
|
39
39
|
|
40
|
-
export
|
40
|
+
export interface ProfilePageProps {
|
41
41
|
username: string;
|
42
|
-
}
|
42
|
+
}
|
43
43
|
|
44
44
|
const ProfilePage = () => {
|
45
45
|
const { username } = useParams<ProfilePageProps>();
|
@@ -141,7 +141,7 @@ const ProfilePage = () => {
|
|
141
141
|
id='edit-profile-button'
|
142
142
|
variant='contained'
|
143
143
|
startIcon={<Edit />}
|
144
|
-
onClick={() => navigate('/profile/edit')}
|
144
|
+
onClick={() => { navigate('/profile/edit'); }}
|
145
145
|
>
|
146
146
|
Edit Profile
|
147
147
|
</Button>
|
@@ -194,7 +194,7 @@ const ProfilePage = () => {
|
|
194
194
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
195
195
|
<Tabs
|
196
196
|
value={searchParams.get('view') || 'stats'}
|
197
|
-
onChange={(_, t) => setSearchParams({ view: t })}
|
197
|
+
onChange={(_, t) => { setSearchParams({ view: t }); }}
|
198
198
|
aria-label='profile tabs'
|
199
199
|
variant='scrollable'
|
200
200
|
>
|
@@ -35,8 +35,8 @@ export function SwitchCohortPrompt() {
|
|
35
35
|
}
|
36
36
|
onClose={
|
37
37
|
showGraduation
|
38
|
-
? () => setHideGraduation(true)
|
39
|
-
: () => setHideDemotion(true)
|
38
|
+
? () => { setHideGraduation(true); }
|
39
|
+
: () => { setHideDemotion(true); }
|
40
40
|
}
|
41
41
|
autoHideDuration={showGraduation ? 6000 : 7000}
|
42
42
|
>
|
@@ -48,7 +48,7 @@ export function SwitchCohortPrompt() {
|
|
48
48
|
<Button
|
49
49
|
color='inherit'
|
50
50
|
size='small'
|
51
|
-
onClick={() => navigate('/profile')}
|
51
|
+
onClick={() => { navigate('/profile'); }}
|
52
52
|
endIcon={<NavigateNextIcon />}
|
53
53
|
>
|
54
54
|
Profile
|
@@ -57,7 +57,7 @@ export function SwitchCohortPrompt() {
|
|
57
57
|
<Button
|
58
58
|
color='inherit'
|
59
59
|
size='small'
|
60
|
-
onClick={() => navigate('/profile/edit')}
|
60
|
+
onClick={() => { navigate('/profile/edit'); }}
|
61
61
|
endIcon={<NavigateNextIcon />}
|
62
62
|
>
|
63
63
|
Settings
|
@@ -169,7 +169,7 @@ const ActivityPieChart: React.FC<ActivityPieChartProps> = ({ user, timeline }) =
|
|
169
169
|
label='Timeframe'
|
170
170
|
value={timeframe}
|
171
171
|
onChange={(event) =>
|
172
|
-
onChangeTimeframe(event.target.value as Timeframe)
|
172
|
+
{ onChangeTimeframe(event.target.value as Timeframe); }
|
173
173
|
}
|
174
174
|
sx={{ mb: 3, height: 1 }}
|
175
175
|
fullWidth
|
@@ -202,7 +202,7 @@ const ActivityPieChart: React.FC<ActivityPieChartProps> = ({ user, timeline }) =
|
|
202
202
|
{Math.round(score * 100) / 100}
|
203
203
|
</Typography>
|
204
204
|
{scoreChartCategory && (
|
205
|
-
<Button onClick={() => setScoreChartCategory('')}>
|
205
|
+
<Button onClick={() => { setScoreChartCategory(''); }}>
|
206
206
|
Back to Cohort
|
207
207
|
</Button>
|
208
208
|
)}
|
@@ -227,7 +227,7 @@ const ActivityPieChart: React.FC<ActivityPieChartProps> = ({ user, timeline }) =
|
|
227
227
|
{getTimeDisplay(time)}
|
228
228
|
</Typography>
|
229
229
|
{timeChartCategory && (
|
230
|
-
<Button onClick={() => setTimeChartCategory('')}>
|
230
|
+
<Button onClick={() => { setTimeChartCategory(''); }}>
|
231
231
|
Back to Cohort
|
232
232
|
</Button>
|
233
233
|
)}
|
@@ -37,7 +37,7 @@ const CreatedAtItem: React.FC<{ user: User; viewer?: User }> = ({ user, viewer }
|
|
37
37
|
requirementName: 'CreatedAt',
|
38
38
|
requirementCategory: 'Welcome to the Dojo',
|
39
39
|
cohort:
|
40
|
-
user.graduationCohorts
|
40
|
+
user.graduationCohorts.length > 0
|
41
41
|
? user.graduationCohorts[0]
|
42
42
|
: user.dojoCohort,
|
43
43
|
};
|
@@ -97,7 +97,7 @@ const ActivityTimeline: React.FC<ActivityTimelineProps> = ({ user, timeline }) =
|
|
97
97
|
<NewsfeedItem
|
98
98
|
key={entry.id}
|
99
99
|
entry={entry}
|
100
|
-
onEdit={(e) => onEdit(i, e)}
|
100
|
+
onEdit={(e) => { onEdit(i, e); }}
|
101
101
|
maxComments={3}
|
102
102
|
/>
|
103
103
|
))}
|
@@ -22,7 +22,7 @@ interface PieChartProps {
|
|
22
22
|
data: PieChartData[];
|
23
23
|
renderTotal: (value: number) => JSX.Element;
|
24
24
|
getTooltip: (entry: PieChartData) => string;
|
25
|
-
onClick: (event: React.MouseEvent
|
25
|
+
onClick: (event: React.MouseEvent, dataIndex: number) => void;
|
26
26
|
}
|
27
27
|
|
28
28
|
const PieChart: React.FC<PieChartProps> = ({
|
@@ -193,12 +193,12 @@ function getTimeframeScoreChartData(
|
|
193
193
|
|
194
194
|
const data: Record<string, PieChartData> = {};
|
195
195
|
const timeCutoff = timeframeToISO(timeframe);
|
196
|
-
const requirementMap = requirements.reduce(
|
196
|
+
const requirementMap = requirements.reduce<Record<string, Requirement>>(
|
197
197
|
(m, r) => {
|
198
198
|
m[r.id] = r;
|
199
199
|
return m;
|
200
200
|
},
|
201
|
-
{}
|
201
|
+
{},
|
202
202
|
);
|
203
203
|
|
204
204
|
for (const entry of timeline) {
|
@@ -270,12 +270,12 @@ function getCategoryScoreChartData(
|
|
270
270
|
|
271
271
|
const data: Record<string, PieChartData> = {};
|
272
272
|
const timeCutoff = timeframeToISO(timeframe);
|
273
|
-
const requirementMap = requirements.reduce(
|
273
|
+
const requirementMap = requirements.reduce<Record<string, Requirement>>(
|
274
274
|
(m, r) => {
|
275
275
|
m[r.id] = r;
|
276
276
|
return m;
|
277
277
|
},
|
278
|
-
{}
|
278
|
+
{},
|
279
279
|
);
|
280
280
|
|
281
281
|
for (const entry of timeline) {
|
@@ -507,12 +507,12 @@ function getAllTimeTimeChartData(
|
|
507
507
|
requirements: Requirement[],
|
508
508
|
): PieChartData[] {
|
509
509
|
const requirementMap =
|
510
|
-
requirements.reduce(
|
510
|
+
requirements.reduce<Record<string, Requirement | CustomTask>>(
|
511
511
|
(map, r) => {
|
512
512
|
map[r.id] = r;
|
513
513
|
return map;
|
514
514
|
},
|
515
|
-
{}
|
515
|
+
{},
|
516
516
|
) ?? {};
|
517
517
|
|
518
518
|
user.customTasks?.forEach((t) => {
|
@@ -638,7 +638,7 @@ function getAllTimeCategoryTimeChartData(
|
|
638
638
|
continue;
|
639
639
|
}
|
640
640
|
const progress = user.progress[requirement.id];
|
641
|
-
if (!progress
|
641
|
+
if (!progress.minutesSpent) {
|
642
642
|
continue;
|
643
643
|
}
|
644
644
|
|
@@ -674,11 +674,11 @@ function getAllTimeCategoryTimeChartData(
|
|
674
674
|
if (category === 'Non-Dojo') {
|
675
675
|
for (const task of user.customTasks || []) {
|
676
676
|
const progress = user.progress[task.id];
|
677
|
-
if (!progress
|
677
|
+
if (!progress.minutesSpent) {
|
678
678
|
continue;
|
679
679
|
}
|
680
680
|
|
681
|
-
|
681
|
+
const name = task.name;
|
682
682
|
|
683
683
|
let reqCohorts = cohorts;
|
684
684
|
if (cohorts.includes(ALL_COHORTS)) {
|
@@ -87,7 +87,7 @@ const DiscordForm: React.FC<ProfileCreatorFormProps> = ({
|
|
87
87
|
<TextField
|
88
88
|
label='Discord Username'
|
89
89
|
value={discordUsername}
|
90
|
-
onChange={(event) => setDiscordUsername(event.target.value)}
|
90
|
+
onChange={(event) => { setDiscordUsername(event.target.value); }}
|
91
91
|
helperText={'Format as username#id for older-style Discord usernames'}
|
92
92
|
/>
|
93
93
|
|
@@ -97,7 +97,7 @@ const DiscordForm: React.FC<ProfileCreatorFormProps> = ({
|
|
97
97
|
<Checkbox
|
98
98
|
checked={!disableBookingNotifications}
|
99
99
|
onChange={(event) =>
|
100
|
-
setDisableBookingNotifications(!event.target.checked)
|
100
|
+
{ setDisableBookingNotifications(!event.target.checked); }
|
101
101
|
}
|
102
102
|
/>
|
103
103
|
}
|
@@ -109,7 +109,7 @@ const DiscordForm: React.FC<ProfileCreatorFormProps> = ({
|
|
109
109
|
<Checkbox
|
110
110
|
checked={!disableCancellationNotifications}
|
111
111
|
onChange={(event) =>
|
112
|
-
setDisableCancellationNotifications(!event.target.checked)
|
112
|
+
{ setDisableCancellationNotifications(!event.target.checked); }
|
113
113
|
}
|
114
114
|
/>
|
115
115
|
}
|
@@ -56,17 +56,17 @@ const ExtraRatingSystemsForm: React.FC<ProfileCreatorFormProps> = ({
|
|
56
56
|
const request = useRequest();
|
57
57
|
|
58
58
|
const [usernames, setUsernames] = useState<Record<RatingSystem, string>>(
|
59
|
-
Object.values(RatingSystems).reduce((map, rs) => {
|
59
|
+
Object.values(RatingSystems).reduce<Record<RatingSystem, string>>((map, rs) => {
|
60
60
|
map[rs] = getRatingUsername(user, rs);
|
61
61
|
return map;
|
62
|
-
}, {}
|
62
|
+
}, {})
|
63
63
|
);
|
64
64
|
|
65
65
|
const [hideUsernames, setHideUsernames] = useState<Record<RatingSystem, boolean>>(
|
66
|
-
Object.values(RatingSystems).reduce((map, rs) => {
|
66
|
+
Object.values(RatingSystems).reduce<Record<RatingSystem, boolean>>((map, rs) => {
|
67
67
|
map[rs] = hideRatingUsername(user, rs);
|
68
68
|
return map;
|
69
|
-
}, {}
|
69
|
+
}, {})
|
70
70
|
);
|
71
71
|
|
72
72
|
const setUsername = (rs: RatingSystem, value: string) => {
|
@@ -123,7 +123,7 @@ const ExtraRatingSystemsForm: React.FC<ProfileCreatorFormProps> = ({
|
|
123
123
|
label={getUsernameLabel(rs)}
|
124
124
|
value={usernames[rs]}
|
125
125
|
onChange={(event) =>
|
126
|
-
setUsername(rs, event.target.value)
|
126
|
+
{ setUsername(rs, event.target.value); }
|
127
127
|
}
|
128
128
|
helperText={getHelperText(rs)}
|
129
129
|
fullWidth
|
@@ -136,7 +136,7 @@ const ExtraRatingSystemsForm: React.FC<ProfileCreatorFormProps> = ({
|
|
136
136
|
<Checkbox
|
137
137
|
checked={hideUsernames[rs]}
|
138
138
|
onChange={(event) =>
|
139
|
-
setHideUsername(rs, event.target.checked)
|
139
|
+
{ setHideUsername(rs, event.target.checked); }
|
140
140
|
}
|
141
141
|
/>
|
142
142
|
}
|
@@ -57,7 +57,7 @@ const PersonalInfoForm: React.FC<ProfileCreatorFormProps> = ({ user, onNextStep
|
|
57
57
|
required
|
58
58
|
label='Display Name'
|
59
59
|
value={displayName}
|
60
|
-
onChange={(event) => setDisplayName(event.target.value)}
|
60
|
+
onChange={(event) => { setDisplayName(event.target.value); }}
|
61
61
|
helperText={'This is how other users will identify you'}
|
62
62
|
/>
|
63
63
|
|
@@ -67,14 +67,14 @@ const PersonalInfoForm: React.FC<ProfileCreatorFormProps> = ({ user, onNextStep
|
|
67
67
|
minRows={3}
|
68
68
|
maxRows={6}
|
69
69
|
value={bio}
|
70
|
-
onChange={(event) => setBio(event.target.value)}
|
70
|
+
onChange={(event) => { setBio(event.target.value); }}
|
71
71
|
/>
|
72
72
|
|
73
73
|
<TextField
|
74
74
|
select
|
75
75
|
label='Timezone (Optional)'
|
76
76
|
value={timezone}
|
77
|
-
onChange={(e) => setTimezone(e.target.value)}
|
77
|
+
onChange={(e) => { setTimezone(e.target.value); }}
|
78
78
|
>
|
79
79
|
{getTimezoneOptions()}
|
80
80
|
</TextField>
|
@@ -158,7 +158,7 @@ const PreferredRatingSystemForm: React.FC<ProfileCreatorFormProps> = ({
|
|
158
158
|
select
|
159
159
|
label='Preferred Rating System'
|
160
160
|
value={ratingSystem}
|
161
|
-
onChange={(event) => setRatingSystem(event.target.value as RatingSystem)}
|
161
|
+
onChange={(event) => { setRatingSystem(event.target.value as RatingSystem); }}
|
162
162
|
helperText='Choose the rating system you play most often'
|
163
163
|
>
|
164
164
|
{Object.values(RatingSystems).map((option) => (
|
@@ -174,7 +174,7 @@ const PreferredRatingSystemForm: React.FC<ProfileCreatorFormProps> = ({
|
|
174
174
|
required
|
175
175
|
label={getUsernameLabel(ratingSystem)}
|
176
176
|
value={username}
|
177
|
-
onChange={(event) => setUsername(event.target.value)}
|
177
|
+
onChange={(event) => { setUsername(event.target.value); }}
|
178
178
|
helperText={getHelperText(ratingSystem)}
|
179
179
|
/>
|
180
180
|
|
@@ -183,7 +183,7 @@ const PreferredRatingSystemForm: React.FC<ProfileCreatorFormProps> = ({
|
|
183
183
|
<Checkbox
|
184
184
|
checked={hideUsername}
|
185
185
|
onChange={(event) =>
|
186
|
-
setHideUsername(event.target.checked)
|
186
|
+
{ setHideUsername(event.target.checked); }
|
187
187
|
}
|
188
188
|
/>
|
189
189
|
}
|
@@ -77,7 +77,7 @@ const ProfileCreatorPage = () => {
|
|
77
77
|
user.subscriptionStatus !== SubscriptionStatus.Subscribed &&
|
78
78
|
activeStep === 0
|
79
79
|
) {
|
80
|
-
return <PricingPage onFreeTier={() => setShowPricingPage(false)} />;
|
80
|
+
return <PricingPage onFreeTier={() => { setShowPricingPage(false); }} />;
|
81
81
|
}
|
82
82
|
|
83
83
|
return (
|
@@ -101,8 +101,8 @@ const ProfileCreatorPage = () => {
|
|
101
101
|
<Box mt={5}>
|
102
102
|
<Form
|
103
103
|
user={user}
|
104
|
-
onNextStep={() => setActiveStep(activeStep + 1)}
|
105
|
-
onPrevStep={() => setActiveStep(activeStep - 1)}
|
104
|
+
onNextStep={() => { setActiveStep(activeStep + 1); }}
|
105
|
+
onPrevStep={() => { setActiveStep(activeStep - 1); }}
|
106
106
|
/>
|
107
107
|
</Box>
|
108
108
|
</Container>
|
@@ -82,7 +82,7 @@ const ReferralSourceForm: React.FC<ProfileCreatorFormProps> = ({ user, onPrevSte
|
|
82
82
|
required
|
83
83
|
label='Referral Source'
|
84
84
|
value={referralSource}
|
85
|
-
onChange={(e) => setReferralSource(e.target.value)}
|
85
|
+
onChange={(e) => { setReferralSource(e.target.value); }}
|
86
86
|
error={!!errors.referralSource}
|
87
87
|
helperText={errors.referralSource}
|
88
88
|
>
|
@@ -100,7 +100,7 @@ const ReferralSourceForm: React.FC<ProfileCreatorFormProps> = ({ user, onPrevSte
|
|
100
100
|
required
|
101
101
|
label='Other (Please Specify)'
|
102
102
|
value={otherSource}
|
103
|
-
onChange={(e) => setOtherSource(e.target.value)}
|
103
|
+
onChange={(e) => { setOtherSource(e.target.value); }}
|
104
104
|
error={!!errors.otherSource}
|
105
105
|
helperText={errors.otherSource}
|
106
106
|
/>
|
@@ -45,7 +45,7 @@ function setSettingValue(
|
|
45
45
|
|
46
46
|
interface NotificationSettingsSection {
|
47
47
|
label: string;
|
48
|
-
settings:
|
48
|
+
settings: { label: string; path: string }[];
|
49
49
|
icon: React.ReactNode;
|
50
50
|
}
|
51
51
|
|
@@ -155,13 +155,13 @@ const NotificationSettingsEditor: React.FC<NotificationSettingsEditorProps> = ({
|
|
155
155
|
)
|
156
156
|
}
|
157
157
|
onChange={(e) =>
|
158
|
-
setNotificationSettings(
|
158
|
+
{ setNotificationSettings(
|
159
159
|
setSettingValue(
|
160
160
|
notificationSettings,
|
161
161
|
setting.path,
|
162
162
|
!e.target.checked,
|
163
163
|
),
|
164
|
-
)
|
164
|
+
); }
|
165
165
|
}
|
166
166
|
/>
|
167
167
|
}
|
@@ -103,7 +103,7 @@ interface RatingEditor {
|
|
103
103
|
function getRatingEditors(ratings: Partial<Record<RatingSystem, Rating>>) {
|
104
104
|
const ratingEditors: Record<RatingSystem, RatingEditor> = Object.values(
|
105
105
|
RatingSystem,
|
106
|
-
).reduce(
|
106
|
+
).reduce<Record<RatingSystem, RatingEditor>>(
|
107
107
|
(m, rs) => {
|
108
108
|
m[rs] = {
|
109
109
|
username: ratings[rs]?.username || '',
|
@@ -114,13 +114,13 @@ function getRatingEditors(ratings: Partial<Record<RatingSystem, Rating>>) {
|
|
114
114
|
};
|
115
115
|
return m;
|
116
116
|
},
|
117
|
-
{}
|
117
|
+
{},
|
118
118
|
);
|
119
119
|
return ratingEditors;
|
120
120
|
}
|
121
121
|
|
122
122
|
function getRatingsFromEditors(ratingEditors: Record<RatingSystem, RatingEditor>) {
|
123
|
-
const ratings: Record<RatingSystem, Rating> = Object.values(RatingSystem).reduce(
|
123
|
+
const ratings: Record<RatingSystem, Rating> = Object.values(RatingSystem).reduce<Record<RatingSystem, Rating>>(
|
124
124
|
(m, rs) => {
|
125
125
|
m[rs] = {
|
126
126
|
username: ratingEditors[rs].username || '',
|
@@ -131,7 +131,7 @@ function getRatingsFromEditors(ratingEditors: Record<RatingSystem, RatingEditor>
|
|
131
131
|
};
|
132
132
|
return m;
|
133
133
|
},
|
134
|
-
{}
|
134
|
+
{},
|
135
135
|
);
|
136
136
|
return ratings;
|
137
137
|
}
|
@@ -146,7 +146,7 @@ function parseRating(rating: string | undefined): number {
|
|
146
146
|
return 0;
|
147
147
|
}
|
148
148
|
rating = rating.replace(/^0+/, '') || '0';
|
149
|
-
|
149
|
+
const n = Math.floor(Number(rating));
|
150
150
|
if (n === Infinity) {
|
151
151
|
return -1;
|
152
152
|
}
|
@@ -196,7 +196,7 @@ function getTimezoneOptions() {
|
|
196
196
|
|
197
197
|
export function encodeFileToBase64(file: File): Promise<string> {
|
198
198
|
return new Promise<string>((resolve, reject) => {
|
199
|
-
|
199
|
+
const reader = new FileReader();
|
200
200
|
reader.onloadend = function () {
|
201
201
|
const base64string = reader.result as string;
|
202
202
|
console.log('Base 64 string: ', base64string);
|
@@ -354,7 +354,7 @@ const ProfileEditorPage = () => {
|
|
354
354
|
|
355
355
|
if (
|
356
356
|
ratingSystem !== RatingSystem.Custom &&
|
357
|
-
!ratingEditors[ratingSystem]
|
357
|
+
!ratingEditors[ratingSystem].username.trim()
|
358
358
|
) {
|
359
359
|
newErrors[`${ratingSystem}Username`] =
|
360
360
|
`This field is required when using ${formatRatingSystem(
|
@@ -363,17 +363,17 @@ const ProfileEditorPage = () => {
|
|
363
363
|
}
|
364
364
|
|
365
365
|
for (const rs of Object.keys(ratingEditors)) {
|
366
|
-
if (parseRating(ratingEditors[rs as RatingSystem]
|
366
|
+
if (parseRating(ratingEditors[rs as RatingSystem].startRating) < 0) {
|
367
367
|
newErrors[`${rs}StartRating`] = 'Rating must be an integer >= 0';
|
368
368
|
}
|
369
369
|
}
|
370
370
|
|
371
371
|
if (ratingSystem === RatingSystem.Custom) {
|
372
|
-
if (parseRating(ratingEditors[RatingSystem.Custom]
|
372
|
+
if (parseRating(ratingEditors[RatingSystem.Custom].currentRating) <= 0) {
|
373
373
|
newErrors.currentCustomRating =
|
374
374
|
'This field is required when using Custom rating system.';
|
375
375
|
}
|
376
|
-
if (parseRating(ratingEditors[RatingSystem.Custom]
|
376
|
+
if (parseRating(ratingEditors[RatingSystem.Custom].startRating) <= 0) {
|
377
377
|
newErrors.startCustomRating =
|
378
378
|
'This field is required when using Custom rating system.';
|
379
379
|
}
|
@@ -410,12 +410,12 @@ const ProfileEditorPage = () => {
|
|
410
410
|
required: ratingSystem === rsf.system,
|
411
411
|
label: rsf.label,
|
412
412
|
hideLabel: rsf.hideLabel,
|
413
|
-
username: ratingEditors[rsf.system]
|
414
|
-
setUsername: (value: string) => setUsername(rsf.system, value),
|
415
|
-
startRating: ratingEditors[rsf.system]
|
416
|
-
setStartRating: (value: string) => setStartRating(rsf.system, value),
|
417
|
-
hidden: ratingEditors[rsf.system]
|
418
|
-
setHidden: (value: boolean) => setHidden(rsf.system, value),
|
413
|
+
username: ratingEditors[rsf.system].username,
|
414
|
+
setUsername: (value: string) => { setUsername(rsf.system, value); },
|
415
|
+
startRating: ratingEditors[rsf.system].startRating,
|
416
|
+
setStartRating: (value: string) => { setStartRating(rsf.system, value); },
|
417
|
+
hidden: ratingEditors[rsf.system].hideUsername,
|
418
|
+
setHidden: (value: boolean) => { setHidden(rsf.system, value); },
|
419
419
|
usernameError: errors[`${rsf.system}Username`],
|
420
420
|
startRatingError: errors[`${rsf.system}StartRating`],
|
421
421
|
}));
|
@@ -555,7 +555,7 @@ const ProfileEditorPage = () => {
|
|
555
555
|
variant='contained'
|
556
556
|
color='error'
|
557
557
|
disableElevation
|
558
|
-
onClick={() => navigate('..')}
|
558
|
+
onClick={() => { navigate('..'); }}
|
559
559
|
startIcon={<NotInterestedIcon />}
|
560
560
|
>
|
561
561
|
Cancel
|
@@ -625,7 +625,7 @@ const ProfileEditorPage = () => {
|
|
625
625
|
required
|
626
626
|
label='Display Name'
|
627
627
|
value={displayName}
|
628
|
-
onChange={(event) => setDisplayName(event.target.value)}
|
628
|
+
onChange={(event) => { setDisplayName(event.target.value); }}
|
629
629
|
error={!!errors.displayName}
|
630
630
|
helperText={
|
631
631
|
errors.displayName ||
|
@@ -637,7 +637,7 @@ const ProfileEditorPage = () => {
|
|
637
637
|
label='Discord Username'
|
638
638
|
value={discordUsername}
|
639
639
|
onChange={(event) =>
|
640
|
-
setDiscordUsername(event.target.value)
|
640
|
+
{ setDiscordUsername(event.target.value); }
|
641
641
|
}
|
642
642
|
error={!!errors.discordUsername}
|
643
643
|
helperText={
|
@@ -652,7 +652,7 @@ const ProfileEditorPage = () => {
|
|
652
652
|
minRows={3}
|
653
653
|
maxRows={6}
|
654
654
|
value={bio}
|
655
|
-
onChange={(event) => setBio(event.target.value)}
|
655
|
+
onChange={(event) => { setBio(event.target.value); }}
|
656
656
|
error={!!errors.bio}
|
657
657
|
helperText={
|
658
658
|
errors.bio ||
|
@@ -667,7 +667,7 @@ const ProfileEditorPage = () => {
|
|
667
667
|
minRows={3}
|
668
668
|
maxRows={6}
|
669
669
|
value={coachBio}
|
670
|
-
onChange={(event) => setCoachBio(event.target.value)}
|
670
|
+
onChange={(event) => { setCoachBio(event.target.value); }}
|
671
671
|
helperText='An optional coaching-specific bio. If included, it will be displayed on the coaching page and on the coach tab on your profile. If not included, the coaching page will use your regular bio and the coach tab on your profile will not have an additional bio.'
|
672
672
|
/>
|
673
673
|
)}
|
@@ -676,7 +676,7 @@ const ProfileEditorPage = () => {
|
|
676
676
|
select
|
677
677
|
label='Timezone'
|
678
678
|
value={timezone}
|
679
|
-
onChange={(e) => setTimezone(e.target.value)}
|
679
|
+
onChange={(e) => { setTimezone(e.target.value); }}
|
680
680
|
>
|
681
681
|
{getTimezoneOptions()}
|
682
682
|
</TextField>
|
@@ -706,7 +706,7 @@ const ProfileEditorPage = () => {
|
|
706
706
|
select
|
707
707
|
label='ChessDojo Cohort'
|
708
708
|
value={dojoCohort}
|
709
|
-
onChange={(event) => setDojoCohort(event.target.value)}
|
709
|
+
onChange={(event) => { setDojoCohort(event.target.value); }}
|
710
710
|
error={!!errors.dojoCohort}
|
711
711
|
helperText={errors.dojoCohort}
|
712
712
|
>
|
@@ -723,7 +723,7 @@ const ProfileEditorPage = () => {
|
|
723
723
|
label='Preferred Rating System'
|
724
724
|
value={ratingSystem}
|
725
725
|
onChange={(event) =>
|
726
|
-
setRatingSystem(event.target.value as RatingSystem)
|
726
|
+
{ setRatingSystem(event.target.value as RatingSystem); }
|
727
727
|
}
|
728
728
|
error={!!errors.ratingSystem}
|
729
729
|
helperText={errors.ratingSystem}
|
@@ -748,7 +748,7 @@ const ProfileEditorPage = () => {
|
|
748
748
|
label={rs.label}
|
749
749
|
value={rs.username}
|
750
750
|
onChange={(event) =>
|
751
|
-
rs.setUsername(event.target.value)
|
751
|
+
{ rs.setUsername(event.target.value); }
|
752
752
|
}
|
753
753
|
error={!!rs.usernameError}
|
754
754
|
helperText={
|
@@ -776,7 +776,7 @@ const ProfileEditorPage = () => {
|
|
776
776
|
label='Start Rating'
|
777
777
|
value={rs.startRating}
|
778
778
|
onChange={(event) =>
|
779
|
-
rs.setStartRating(event.target.value)
|
779
|
+
{ rs.setStartRating(event.target.value); }
|
780
780
|
}
|
781
781
|
error={!!rs.startRatingError}
|
782
782
|
helperText={
|
@@ -793,7 +793,7 @@ const ProfileEditorPage = () => {
|
|
793
793
|
<Checkbox
|
794
794
|
checked={rs.hidden}
|
795
795
|
onChange={(event) =>
|
796
|
-
rs.setHidden(event.target.checked)
|
796
|
+
{ rs.setHidden(event.target.checked); }
|
797
797
|
}
|
798
798
|
/>
|
799
799
|
}
|
@@ -810,13 +810,13 @@ const ProfileEditorPage = () => {
|
|
810
810
|
label='Current Rating (Custom)'
|
811
811
|
value={
|
812
812
|
ratingEditors[RatingSystem.Custom]
|
813
|
-
|
813
|
+
.currentRating
|
814
814
|
}
|
815
815
|
onChange={(event) =>
|
816
|
-
setCurrentRating(
|
816
|
+
{ setCurrentRating(
|
817
817
|
RatingSystem.Custom,
|
818
818
|
event.target.value,
|
819
|
-
)
|
819
|
+
); }
|
820
820
|
}
|
821
821
|
error={!!errors.currentCustomRating}
|
822
822
|
helperText={
|
@@ -833,13 +833,13 @@ const ProfileEditorPage = () => {
|
|
833
833
|
label='Start Rating (Custom)'
|
834
834
|
value={
|
835
835
|
ratingEditors[RatingSystem.Custom]
|
836
|
-
|
836
|
+
.startRating
|
837
837
|
}
|
838
838
|
onChange={(event) =>
|
839
|
-
setStartRating(
|
839
|
+
{ setStartRating(
|
840
840
|
RatingSystem.Custom,
|
841
841
|
event.target.value,
|
842
|
-
)
|
842
|
+
); }
|
843
843
|
}
|
844
844
|
error={!!errors.startCustomRating}
|
845
845
|
helperText={
|
@@ -853,12 +853,12 @@ const ProfileEditorPage = () => {
|
|
853
853
|
<Grid item xs>
|
854
854
|
<TextField
|
855
855
|
label='Custom Rating Name'
|
856
|
-
value={ratingEditors[RatingSystem.Custom]
|
856
|
+
value={ratingEditors[RatingSystem.Custom].name}
|
857
857
|
onChange={(event) =>
|
858
|
-
setRatingName(
|
858
|
+
{ setRatingName(
|
859
859
|
RatingSystem.Custom,
|
860
860
|
event.target.value,
|
861
|
-
)
|
861
|
+
); }
|
862
862
|
}
|
863
863
|
sx={{ width: 1 }}
|
864
864
|
helperText=' '
|
@@ -897,7 +897,7 @@ const ProfileEditorPage = () => {
|
|
897
897
|
<Checkbox
|
898
898
|
checked={enableLightMode}
|
899
899
|
onChange={(event) =>
|
900
|
-
setEnableLightMode(event.target.checked)
|
900
|
+
{ setEnableLightMode(event.target.checked); }
|
901
901
|
}
|
902
902
|
/>
|
903
903
|
}
|
@@ -54,7 +54,7 @@ const SubscriptionManager: React.FC<SubscriptionManagerProps> = ({ user }) => {
|
|
54
54
|
{isFreeTier ? (
|
55
55
|
<>
|
56
56
|
<Typography>Subscription Status: Free Tier</Typography>
|
57
|
-
<Button variant='contained' onClick={() => navigate('/prices')}>
|
57
|
+
<Button variant='contained' onClick={() => { navigate('/prices'); }}>
|
58
58
|
View Prices
|
59
59
|
</Button>
|
60
60
|
</>
|
@@ -62,7 +62,7 @@ const SubscriptionManager: React.FC<SubscriptionManagerProps> = ({ user }) => {
|
|
62
62
|
<>
|
63
63
|
<Typography>Subscription Status: Subscribed</Typography>
|
64
64
|
|
65
|
-
{paymentInfo
|
65
|
+
{paymentInfo?.customerId ? (
|
66
66
|
<LoadingButton
|
67
67
|
loading={request.isLoading()}
|
68
68
|
onClick={onManageSubscription}
|
@@ -41,7 +41,7 @@ const FollowersList = () => {
|
|
41
41
|
return <LoadingPage />;
|
42
42
|
}
|
43
43
|
|
44
|
-
if (!request.data
|
44
|
+
if (!request.data?.followers || request.data.followers.length === 0) {
|
45
45
|
return (
|
46
46
|
<>
|
47
47
|
<RequestSnackbar request={request} />
|
@@ -8,7 +8,7 @@ interface CoachChipProps {
|
|
8
8
|
}
|
9
9
|
|
10
10
|
const CoachChip: React.FC<CoachChipProps> = ({ user }) => {
|
11
|
-
if (!user
|
11
|
+
if (!user?.isCoach) {
|
12
12
|
return null;
|
13
13
|
}
|
14
14
|
|
@@ -2,9 +2,9 @@ import { faDiscord } from '@fortawesome/free-brands-svg-icons';
|
|
2
2
|
import { Chip, SvgIcon, Tooltip } from '@mui/material';
|
3
3
|
import { forwardRef } from 'react';
|
4
4
|
|
5
|
-
|
5
|
+
interface FontAwesomeSvgIconProps {
|
6
6
|
icon: any;
|
7
|
-
}
|
7
|
+
}
|
8
8
|
|
9
9
|
export const FontAwesomeSvgIcon = forwardRef<SVGSVGElement, FontAwesomeSvgIconProps>(
|
10
10
|
(props, ref) => {
|
@@ -41,12 +41,12 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
41
41
|
task ? Object.values(task.counts).length === dojoCohorts.length : true,
|
42
42
|
);
|
43
43
|
const [cohorts, setCohorts] = useState<Record<string, boolean>>(
|
44
|
-
dojoCohorts.reduce(
|
44
|
+
dojoCohorts.reduce<Record<string, boolean>>(
|
45
45
|
(map, cohort) => {
|
46
46
|
map[cohort] = task?.counts[cohort] !== undefined || false;
|
47
47
|
return map;
|
48
48
|
},
|
49
|
-
{}
|
49
|
+
{},
|
50
50
|
),
|
51
51
|
);
|
52
52
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
@@ -75,12 +75,12 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
75
75
|
const includedCohorts = allCohorts
|
76
76
|
? dojoCohorts
|
77
77
|
: Object.keys(cohorts).filter((c) => cohorts[c]);
|
78
|
-
const newCounts = includedCohorts.reduce(
|
78
|
+
const newCounts = includedCohorts.reduce<Record<string, number>>(
|
79
79
|
(map, c) => {
|
80
80
|
map[c] = 1;
|
81
81
|
return map;
|
82
82
|
},
|
83
|
-
{}
|
83
|
+
{},
|
84
84
|
);
|
85
85
|
|
86
86
|
const newTask = {
|
@@ -152,7 +152,7 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
152
152
|
label='Activity Name'
|
153
153
|
required
|
154
154
|
value={name}
|
155
|
-
onChange={(e) => setName(e.target.value)}
|
155
|
+
onChange={(e) => { setName(e.target.value); }}
|
156
156
|
error={!!errors.name}
|
157
157
|
helperText={errors.name}
|
158
158
|
fullWidth
|
@@ -164,7 +164,7 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
164
164
|
minRows={3}
|
165
165
|
maxRows={3}
|
166
166
|
value={description}
|
167
|
-
onChange={(e) => setDescription(e.target.value)}
|
167
|
+
onChange={(e) => { setDescription(e.target.value); }}
|
168
168
|
fullWidth
|
169
169
|
/>
|
170
170
|
|
@@ -179,7 +179,7 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
179
179
|
<Checkbox
|
180
180
|
checked={allCohorts}
|
181
181
|
onChange={(event) =>
|
182
|
-
setAllCohorts(event.target.checked)
|
182
|
+
{ setAllCohorts(event.target.checked); }
|
183
183
|
}
|
184
184
|
/>
|
185
185
|
}
|
@@ -199,10 +199,10 @@ const CustomTaskEditor: React.FC<CustomTaskEditorProps> = ({ task, open, onClose
|
|
199
199
|
allCohorts || cohorts[cohort]
|
200
200
|
}
|
201
201
|
onChange={(event) =>
|
202
|
-
onChangeCohort(
|
202
|
+
{ onChangeCohort(
|
203
203
|
cohort,
|
204
204
|
event.target.checked,
|
205
|
-
)
|
205
|
+
); }
|
206
206
|
}
|
207
207
|
/>
|
208
208
|
}
|
@@ -40,7 +40,7 @@ const CustomTaskProgressItem: React.FC<CustomTaskProgressItemProps> = ({
|
|
40
40
|
<Grid
|
41
41
|
item
|
42
42
|
xs={9}
|
43
|
-
onClick={() => setShowReqModal(true)}
|
43
|
+
onClick={() => { setShowReqModal(true); }}
|
44
44
|
sx={{ cursor: 'pointer', position: 'relative' }}
|
45
45
|
>
|
46
46
|
<Typography>{task.name}</Typography>
|
@@ -79,7 +79,7 @@ const CustomTaskProgressItem: React.FC<CustomTaskProgressItemProps> = ({
|
|
79
79
|
|
80
80
|
<IconButton
|
81
81
|
aria-label={`Update ${task.name}`}
|
82
|
-
onClick={() => setShowUpdateDialog(true)}
|
82
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
83
83
|
>
|
84
84
|
<EditIcon />
|
85
85
|
</IconButton>
|
@@ -91,7 +91,7 @@ const CustomTaskProgressItem: React.FC<CustomTaskProgressItemProps> = ({
|
|
91
91
|
{showReqModal && (
|
92
92
|
<RequirementModal
|
93
93
|
open={showReqModal}
|
94
|
-
onClose={() => setShowReqModal(false)}
|
94
|
+
onClose={() => { setShowReqModal(false); }}
|
95
95
|
requirement={task}
|
96
96
|
/>
|
97
97
|
)}
|
@@ -99,7 +99,7 @@ const CustomTaskProgressItem: React.FC<CustomTaskProgressItemProps> = ({
|
|
99
99
|
{showUpdateDialog && (
|
100
100
|
<ProgressDialog
|
101
101
|
open={showUpdateDialog}
|
102
|
-
onClose={() => setShowUpdateDialog(false)}
|
102
|
+
onClose={() => { setShowUpdateDialog(false); }}
|
103
103
|
requirement={task}
|
104
104
|
cohort={cohort}
|
105
105
|
progress={progress}
|
@@ -19,7 +19,7 @@ import ProgressItem from './ProgressItem';
|
|
19
19
|
|
20
20
|
export interface Category {
|
21
21
|
name: string;
|
22
|
-
requirements:
|
22
|
+
requirements: (Requirement | CustomTask)[];
|
23
23
|
totalComplete: number;
|
24
24
|
}
|
25
25
|
|
@@ -67,7 +67,7 @@ const DefaultProgressCategory: React.FC<ProgressCategoryProps> = ({
|
|
67
67
|
<Accordion
|
68
68
|
key={c.name}
|
69
69
|
expanded={expanded}
|
70
|
-
onChange={() => toggleExpand(c.name)}
|
70
|
+
onChange={() => { toggleExpand(c.name); }}
|
71
71
|
sx={{ width: 1 }}
|
72
72
|
>
|
73
73
|
<AccordionSummary
|
@@ -118,7 +118,7 @@ const DefaultProgressCategory: React.FC<ProgressCategoryProps> = ({
|
|
118
118
|
})}
|
119
119
|
|
120
120
|
{!isFreeTier && c.name === 'Non-Dojo' && isCurrentUser && (
|
121
|
-
<Button sx={{ mt: 2 }} onClick={() => setShowCustomTaskEditor(true)}>
|
121
|
+
<Button sx={{ mt: 2 }} onClick={() => { setShowCustomTaskEditor(true); }}>
|
122
122
|
Add Custom Activity
|
123
123
|
</Button>
|
124
124
|
)}
|
@@ -68,7 +68,7 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
|
68
68
|
select
|
69
69
|
label='Cohort'
|
70
70
|
value={selectedCohort}
|
71
|
-
onChange={(event) => setSelectedCohort(event.target.value)}
|
71
|
+
onChange={(event) => { setSelectedCohort(event.target.value); }}
|
72
72
|
sx={{ mt: 1 }}
|
73
73
|
fullWidth
|
74
74
|
>
|
@@ -85,7 +85,7 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
|
85
85
|
<ProgressHistory
|
86
86
|
requirement={requirement}
|
87
87
|
onClose={onClose}
|
88
|
-
toggleView={() => setView(ProgressDialogView.Updater)}
|
88
|
+
toggleView={() => { setView(ProgressDialogView.Updater); }}
|
89
89
|
cohort={selectedCohort}
|
90
90
|
/>
|
91
91
|
)}
|
@@ -95,7 +95,7 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
|
95
95
|
progress={progress}
|
96
96
|
cohort={selectedCohort}
|
97
97
|
onClose={onClose}
|
98
|
-
toggleView={() => setView(ProgressDialogView.History)}
|
98
|
+
toggleView={() => { setView(ProgressDialogView.History); }}
|
99
99
|
/>
|
100
100
|
)}
|
101
101
|
</Dialog>
|
@@ -140,7 +140,7 @@ const ProgressHistoryItem: React.FC<ProgressHistoryItemProps> = ({
|
|
140
140
|
<TextField
|
141
141
|
label='Count'
|
142
142
|
value={count}
|
143
|
-
onChange={(event) => onChangeCount(event.target.value)}
|
143
|
+
onChange={(event) => { onChangeCount(event.target.value); }}
|
144
144
|
sx={{ maxWidth: '100px' }}
|
145
145
|
error={!!error.count}
|
146
146
|
helperText={error.count}
|
@@ -154,7 +154,7 @@ const ProgressHistoryItem: React.FC<ProgressHistoryItemProps> = ({
|
|
154
154
|
inputMode: 'numeric',
|
155
155
|
pattern: '[0-9]*',
|
156
156
|
}}
|
157
|
-
onChange={(event) => onChangeHours(event.target.value)}
|
157
|
+
onChange={(event) => { onChangeHours(event.target.value); }}
|
158
158
|
sx={{ maxWidth: '100px' }}
|
159
159
|
error={!!error.hours}
|
160
160
|
helperText={error.hours}
|
@@ -167,7 +167,7 @@ const ProgressHistoryItem: React.FC<ProgressHistoryItemProps> = ({
|
|
167
167
|
inputMode: 'numeric',
|
168
168
|
pattern: '[0-9]*',
|
169
169
|
}}
|
170
|
-
onChange={(event) => onChangeMinutes(event.target.value)}
|
170
|
+
onChange={(event) => { onChangeMinutes(event.target.value); }}
|
171
171
|
sx={{ maxWidth: '100px' }}
|
172
172
|
error={!!error.minutes}
|
173
173
|
helperText={error.minutes}
|
@@ -201,7 +201,7 @@ function getTimelineUpdate(items: HistoryItem[]): {
|
|
201
201
|
return;
|
202
202
|
}
|
203
203
|
|
204
|
-
|
204
|
+
const itemErrors: HistoryItemError = {};
|
205
205
|
if (item.date === null) {
|
206
206
|
itemErrors.date = 'This field is required';
|
207
207
|
}
|
@@ -357,20 +357,20 @@ const ProgressHistory: React.FC<ProgressHistoryProps> = ({
|
|
357
357
|
|
358
358
|
const getUpdateItem = useCallback(
|
359
359
|
(idx: number) => (item: HistoryItem) =>
|
360
|
-
setItems((items) => [...items.slice(0, idx), item, ...items.slice(idx + 1)]),
|
360
|
+
{ setItems((items) => [...items.slice(0, idx), item, ...items.slice(idx + 1)]); },
|
361
361
|
[setItems],
|
362
362
|
);
|
363
363
|
|
364
364
|
const getDeleteItem = useCallback(
|
365
365
|
(idx: number) => () =>
|
366
|
-
setItems((items) => [
|
366
|
+
{ setItems((items) => [
|
367
367
|
...items.slice(0, idx),
|
368
368
|
{
|
369
369
|
...items[idx],
|
370
370
|
deleted: true,
|
371
371
|
},
|
372
372
|
...items.slice(idx + 1),
|
373
|
-
]),
|
373
|
+
]); },
|
374
374
|
[setItems],
|
375
375
|
);
|
376
376
|
|
@@ -95,12 +95,12 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
95
95
|
return { isBlocked: false };
|
96
96
|
}
|
97
97
|
|
98
|
-
const requirementMap = requirements.reduce(
|
98
|
+
const requirementMap = requirements.reduce<Record<string, Requirement>>(
|
99
99
|
(acc, r) => {
|
100
100
|
acc[r.id] = r;
|
101
101
|
return acc;
|
102
102
|
},
|
103
|
-
{}
|
103
|
+
{},
|
104
104
|
);
|
105
105
|
for (const blockerId of requirement.blockers) {
|
106
106
|
const blocker = requirementMap[blockerId];
|
@@ -129,7 +129,7 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
129
129
|
<Checkbox
|
130
130
|
aria-label={`Checkbox ${requirement.name}`}
|
131
131
|
checked={currentCount >= totalCount}
|
132
|
-
onClick={() => setShowUpdateDialog(true)}
|
132
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
133
133
|
disabled={!isCurrentUser}
|
134
134
|
/>
|
135
135
|
);
|
@@ -149,11 +149,11 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
149
149
|
);
|
150
150
|
UpdateElement =
|
151
151
|
currentCount >= totalCount ? (
|
152
|
-
<Checkbox checked onClick={() => setShowUpdateDialog(true)} />
|
152
|
+
<Checkbox checked onClick={() => { setShowUpdateDialog(true); }} />
|
153
153
|
) : !isCurrentUser ? null : (
|
154
154
|
<IconButton
|
155
155
|
aria-label={`Update ${requirement.name}`}
|
156
|
-
onClick={() => setShowUpdateDialog(true)}
|
156
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
157
157
|
>
|
158
158
|
<EditIcon />
|
159
159
|
</IconButton>
|
@@ -164,7 +164,7 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
164
164
|
UpdateElement = (
|
165
165
|
<IconButton
|
166
166
|
aria-label={`Update ${requirement.name}`}
|
167
|
-
onClick={() => setShowUpdateDialog(true)}
|
167
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
168
168
|
>
|
169
169
|
<EditIcon />
|
170
170
|
</IconButton>
|
@@ -187,7 +187,7 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
187
187
|
{showUpdateDialog && (
|
188
188
|
<ProgressDialog
|
189
189
|
open={showUpdateDialog}
|
190
|
-
onClose={() => setShowUpdateDialog(false)}
|
190
|
+
onClose={() => { setShowUpdateDialog(false); }}
|
191
191
|
requirement={requirement}
|
192
192
|
cohort={cohort}
|
193
193
|
progress={progress}
|
@@ -208,7 +208,7 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
208
208
|
? 9
|
209
209
|
: 10
|
210
210
|
}
|
211
|
-
onClick={() => setShowReqModal(true)}
|
211
|
+
onClick={() => { setShowReqModal(true); }}
|
212
212
|
sx={{ cursor: 'pointer', position: 'relative' }}
|
213
213
|
id='task-details'
|
214
214
|
>
|
@@ -292,7 +292,7 @@ const RequirementProgressItem: React.FC<RequirementProgressItemProps> = ({
|
|
292
292
|
{showReqModal && (
|
293
293
|
<RequirementModal
|
294
294
|
open={showReqModal}
|
295
|
-
onClose={() => setShowReqModal(false)}
|
295
|
+
onClose={() => { setShowReqModal(false); }}
|
296
296
|
requirement={requirement}
|
297
297
|
/>
|
298
298
|
)}
|
@@ -32,7 +32,7 @@ function useHideCompleted(isCurrentUser: boolean) {
|
|
32
32
|
|
33
33
|
interface Category {
|
34
34
|
name: string;
|
35
|
-
requirements:
|
35
|
+
requirements: (Requirement | CustomTask)[];
|
36
36
|
totalComplete: number;
|
37
37
|
}
|
38
38
|
|
@@ -63,7 +63,7 @@ const ProgressTab: React.FC<ProgressTabProps> = ({ user, isCurrentUser }) => {
|
|
63
63
|
|
64
64
|
const categories = useMemo(() => {
|
65
65
|
const categories: Category[] = [];
|
66
|
-
requirements
|
66
|
+
requirements.forEach((r) => {
|
67
67
|
const c = categories.find((c) => c.name === r.category);
|
68
68
|
const complete = isComplete(cohort, r, user.progress[r.id]);
|
69
69
|
if (complete && hideCompleted === 'true') {
|
@@ -148,7 +148,7 @@ const ProgressTab: React.FC<ProgressTabProps> = ({ user, isCurrentUser }) => {
|
|
148
148
|
select
|
149
149
|
label='Cohort'
|
150
150
|
value={cohort}
|
151
|
-
onChange={(event) => onChangeCohort(event.target.value)}
|
151
|
+
onChange={(event) => { onChangeCohort(event.target.value); }}
|
152
152
|
sx={{ mb: 3 }}
|
153
153
|
fullWidth
|
154
154
|
>
|
@@ -180,7 +180,7 @@ const ProgressTab: React.FC<ProgressTabProps> = ({ user, isCurrentUser }) => {
|
|
180
180
|
<Checkbox
|
181
181
|
size='small'
|
182
182
|
checked={hideCompleted === 'true'}
|
183
|
-
onChange={(e) => setHideCompleted(`${e.target.checked}`)}
|
183
|
+
onChange={(e) => { setHideCompleted(`${e.target.checked}`); }}
|
184
184
|
/>
|
185
185
|
}
|
186
186
|
label='Hide Completed Tasks'
|
@@ -214,7 +214,7 @@ const ProgressTab: React.FC<ProgressTabProps> = ({ user, isCurrentUser }) => {
|
|
214
214
|
|
215
215
|
<CustomTaskEditor
|
216
216
|
open={showCustomTaskEditor}
|
217
|
-
onClose={() => setShowCustomTaskEditor(false)}
|
217
|
+
onClose={() => { setShowCustomTaskEditor(false); }}
|
218
218
|
/>
|
219
219
|
</Stack>
|
220
220
|
);
|
@@ -113,8 +113,8 @@ const ProgressUpdater: React.FC<ProgressUpdaterProps> = ({
|
|
113
113
|
const isNonDojo = requirement.scoreboardDisplay === ScoreboardDisplay.NonDojo;
|
114
114
|
const isMinutes = requirement.scoreboardDisplay === ScoreboardDisplay.Minutes;
|
115
115
|
|
116
|
-
|
117
|
-
|
116
|
+
const hoursInt = parseInt(hours) || 0;
|
117
|
+
const minutesInt = parseInt(minutes) || 0;
|
118
118
|
const totalTime = 60 * hoursInt + minutesInt + (progress?.minutesSpent[cohort] ?? 0);
|
119
119
|
const addedTime = 60 * hoursInt + minutesInt;
|
120
120
|
|
@@ -198,7 +198,7 @@ const ProgressUpdater: React.FC<ProgressUpdaterProps> = ({
|
|
198
198
|
<Checkbox
|
199
199
|
checked={markComplete}
|
200
200
|
onChange={(event) =>
|
201
|
-
setMarkComplete(event.target.checked)
|
201
|
+
{ setMarkComplete(event.target.checked); }
|
202
202
|
}
|
203
203
|
/>
|
204
204
|
}
|
@@ -212,7 +212,7 @@ const ProgressUpdater: React.FC<ProgressUpdaterProps> = ({
|
|
212
212
|
multiline={true}
|
213
213
|
maxRows={3}
|
214
214
|
value={notes}
|
215
|
-
onChange={(e) => setNotes(e.target.value)}
|
215
|
+
onChange={(e) => { setNotes(e.target.value); }}
|
216
216
|
/>
|
217
217
|
|
218
218
|
<Stack spacing={2}>
|
@@ -238,7 +238,7 @@ const ProgressUpdater: React.FC<ProgressUpdaterProps> = ({
|
|
238
238
|
inputMode: 'numeric',
|
239
239
|
pattern: '[0-9]*',
|
240
240
|
}}
|
241
|
-
onChange={(event) => setHours(event.target.value)}
|
241
|
+
onChange={(event) => { setHours(event.target.value); }}
|
242
242
|
error={!!errors.hours}
|
243
243
|
helperText={errors.hours}
|
244
244
|
fullWidth
|
@@ -252,7 +252,7 @@ const ProgressUpdater: React.FC<ProgressUpdaterProps> = ({
|
|
252
252
|
inputMode: 'numeric',
|
253
253
|
pattern: '[0-9]*',
|
254
254
|
}}
|
255
|
-
onChange={(event) => setMinutes(event.target.value)}
|
255
|
+
onChange={(event) => { setMinutes(event.target.value); }}
|
256
256
|
error={!!errors.minutes}
|
257
257
|
helperText={errors.minutes}
|
258
258
|
fullWidth
|
@@ -77,7 +77,7 @@ export function getChartData(
|
|
77
77
|
let data = [];
|
78
78
|
|
79
79
|
if (dates.length === ratingHistory.length) {
|
80
|
-
data = ratingHistory
|
80
|
+
data = ratingHistory.map((r) => ({
|
81
81
|
date: new Date(r.date),
|
82
82
|
rating: r.rating,
|
83
83
|
}));
|
@@ -151,7 +151,7 @@ export const primaryAxis: AxisOptions<Datum> = {
|
|
151
151
|
getValue: (datum) => datum.date,
|
152
152
|
};
|
153
153
|
|
154
|
-
export const secondaryAxes:
|
154
|
+
export const secondaryAxes: AxisOptions<Datum>[] = [
|
155
155
|
{
|
156
156
|
scaleType: 'linear',
|
157
157
|
getValue: (datum) => datum.rating,
|
@@ -19,7 +19,7 @@ export const primaryAxis: AxisOptions<Datum> = {
|
|
19
19
|
getValue: (datum) => datum.primary,
|
20
20
|
};
|
21
21
|
|
22
|
-
export const secondaryAxes:
|
22
|
+
export const secondaryAxes: AxisOptions<Datum>[] = [
|
23
23
|
{
|
24
24
|
position: 'bottom',
|
25
25
|
getValue: (datum) => datum.secondary,
|
@@ -42,7 +42,7 @@ export function getCategoryData(
|
|
42
42
|
label,
|
43
43
|
data: categories.reverse().map((category) => ({
|
44
44
|
primary: category,
|
45
|
-
secondary: data.byCategory
|
45
|
+
secondary: data.byCategory[category] || 0,
|
46
46
|
})),
|
47
47
|
},
|
48
48
|
];
|
@@ -71,7 +71,7 @@ export function getMonthData(label: string, data: YearReviewDataSection) {
|
|
71
71
|
.sort((lhs, rhs) => rhs[1].localeCompare(lhs[1]))
|
72
72
|
.map((month) => ({
|
73
73
|
primary: month[0],
|
74
|
-
secondary: data.byPeriod
|
74
|
+
secondary: data.byPeriod[month[1]] || 0,
|
75
75
|
})),
|
76
76
|
},
|
77
77
|
];
|
@@ -16,7 +16,7 @@ import { formatTime } from '../../database/requirement';
|
|
16
16
|
import { CategoryColors } from '../activity/activity';
|
17
17
|
import Percentiles from './Percentiles';
|
18
18
|
|
19
|
-
const secondaryAxes:
|
19
|
+
const secondaryAxes: AxisOptions<Datum>[] = [
|
20
20
|
{
|
21
21
|
position: 'bottom',
|
22
22
|
getValue: (datum) => datum.secondary,
|
@@ -19,10 +19,10 @@ export interface SectionProps {
|
|
19
19
|
review: YearReview;
|
20
20
|
}
|
21
21
|
|
22
|
-
|
22
|
+
interface YearReviewPageParams {
|
23
23
|
username: string;
|
24
24
|
year: string;
|
25
|
-
}
|
25
|
+
}
|
26
26
|
|
27
27
|
const YearReviewPage = () => {
|
28
28
|
const { username, year } = useParams<YearReviewPageParams>();
|
@@ -51,17 +51,17 @@ declare module "*.svg" {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
declare module "*.module.css" {
|
54
|
-
const classes:
|
54
|
+
const classes: Readonly<Record<string, string>>;
|
55
55
|
export default classes;
|
56
56
|
}
|
57
57
|
|
58
58
|
declare module "*.module.scss" {
|
59
|
-
const classes:
|
59
|
+
const classes: Readonly<Record<string, string>>;
|
60
60
|
export default classes;
|
61
61
|
}
|
62
62
|
|
63
63
|
declare module "*.module.sass" {
|
64
|
-
const classes:
|
64
|
+
const classes: Readonly<Record<string, string>>;
|
65
65
|
export default classes;
|
66
66
|
}
|
67
67
|
|
@@ -18,7 +18,7 @@ const FeaturedGames = () => {
|
|
18
18
|
if (!request.isSent()) {
|
19
19
|
request.onStart();
|
20
20
|
api.listFeaturedGames()
|
21
|
-
.then((games) => request.onSuccess(games))
|
21
|
+
.then((games) => { request.onSuccess(games); })
|
22
22
|
.catch((err) => {
|
23
23
|
console.error('listFeaturedGames: ', err);
|
24
24
|
request.onFailure(err);
|
@@ -54,7 +54,7 @@ function getTimeframeOptions() {
|
|
54
54
|
const options: TimeframeOption[] = [];
|
55
55
|
|
56
56
|
for (let i = 0; i < numberOfOptions; i++) {
|
57
|
-
|
57
|
+
const prevGraduation = new Date(currGraduation);
|
58
58
|
prevGraduation.setUTCDate(prevGraduation.getUTCDate() - 7);
|
59
59
|
|
60
60
|
options.push({
|
@@ -186,7 +186,7 @@ const RecentGraduates = () => {
|
|
186
186
|
if (!request.isSent()) {
|
187
187
|
request.onStart();
|
188
188
|
api.listGraduationsByDate()
|
189
|
-
.then((graduations) => request.onSuccess(graduations))
|
189
|
+
.then((graduations) => { request.onSuccess(graduations); })
|
190
190
|
.catch((err) => {
|
191
191
|
console.error('listGraduationsByDate: ', err);
|
192
192
|
request.onFailure(err);
|
@@ -218,7 +218,7 @@ const RecentGraduates = () => {
|
|
218
218
|
>
|
219
219
|
<Select
|
220
220
|
value={timeframe}
|
221
|
-
onChange={(e) => setTimeframe(e.target.value as Timeframe)}
|
221
|
+
onChange={(e) => { setTimeframe(e.target.value as Timeframe); }}
|
222
222
|
sx={{
|
223
223
|
'::before': {
|
224
224
|
border: 'none !important',
|
@@ -37,14 +37,14 @@ const CustomTaskDisplay: React.FC<CustomTaskDisplayProps> = ({ task, onClose })
|
|
37
37
|
<Stack direction='row' spacing={2}>
|
38
38
|
<Button
|
39
39
|
variant='contained'
|
40
|
-
onClick={() => setShowEditor(true)}
|
40
|
+
onClick={() => { setShowEditor(true); }}
|
41
41
|
>
|
42
42
|
Edit Task
|
43
43
|
</Button>
|
44
44
|
<Button
|
45
45
|
variant='contained'
|
46
46
|
color='error'
|
47
|
-
onClick={() => setShowDeleter(true)}
|
47
|
+
onClick={() => { setShowDeleter(true); }}
|
48
48
|
>
|
49
49
|
Delete Task
|
50
50
|
</Button>
|
@@ -59,14 +59,14 @@ const CustomTaskDisplay: React.FC<CustomTaskDisplayProps> = ({ task, onClose })
|
|
59
59
|
|
60
60
|
<CustomTaskEditor
|
61
61
|
open={showEditor}
|
62
|
-
onClose={() => setShowEditor(false)}
|
62
|
+
onClose={() => { setShowEditor(false); }}
|
63
63
|
task={task}
|
64
64
|
/>
|
65
65
|
|
66
66
|
<DeleteCustomTaskModal
|
67
67
|
task={task}
|
68
68
|
open={showDeleter}
|
69
|
-
onCancel={() => setShowDeleter(false)}
|
69
|
+
onCancel={() => { setShowDeleter(false); }}
|
70
70
|
onDelete={onClose}
|
71
71
|
/>
|
72
72
|
</>
|
@@ -59,7 +59,7 @@ const Position: React.FC<PositionProps> = ({ position, orientation }) => {
|
|
59
59
|
'clock.limit': position.limitSeconds,
|
60
60
|
'clock.increment': position.incrementSeconds,
|
61
61
|
fen: position.fen.trim(),
|
62
|
-
name:
|
62
|
+
name: position.title,
|
63
63
|
})
|
64
64
|
.then((resp) => {
|
65
65
|
console.log('Generate Lichess URL: ', resp);
|
@@ -43,7 +43,7 @@ function dojoPointDescription(requirement: Requirement, cohort: string) {
|
|
43
43
|
unit = 'percentage';
|
44
44
|
} else if (requirement.progressBarSuffix) {
|
45
45
|
unit = requirement.progressBarSuffix.toLowerCase();
|
46
|
-
if (unit
|
46
|
+
if (unit.endsWith('s')) {
|
47
47
|
unit = unit.substring(0, unit.length - 1);
|
48
48
|
}
|
49
49
|
}
|
@@ -133,12 +133,12 @@ const RepeatChip: React.FC<{ requirement: Requirement }> = ({ requirement }) =>
|
|
133
133
|
const BlockerChips: React.FC<{ requirement: Requirement }> = ({ requirement }) => {
|
134
134
|
const { requirements } = useRequirements(ALL_COHORTS, false);
|
135
135
|
const requirementMap = useMemo(() => {
|
136
|
-
return requirements.reduce(
|
136
|
+
return requirements.reduce<Record<string, Requirement>>(
|
137
137
|
(acc, r) => {
|
138
138
|
acc[r.id] = r;
|
139
139
|
return acc;
|
140
140
|
},
|
141
|
-
{}
|
141
|
+
{},
|
142
142
|
);
|
143
143
|
}, [requirements]);
|
144
144
|
|
@@ -203,12 +203,12 @@ const RequirementDisplay: React.FC<RequirementDisplayProps> = ({
|
|
203
203
|
return { isBlocked: false };
|
204
204
|
}
|
205
205
|
|
206
|
-
const requirementMap = requirements.reduce(
|
206
|
+
const requirementMap = requirements.reduce<Record<string, Requirement>>(
|
207
207
|
(acc, r) => {
|
208
208
|
acc[r.id] = r;
|
209
209
|
return acc;
|
210
210
|
},
|
211
|
-
{}
|
211
|
+
{},
|
212
212
|
);
|
213
213
|
for (const blockerId of requirement.blockers) {
|
214
214
|
const blocker = requirementMap[blockerId];
|
@@ -229,7 +229,7 @@ const RequirementDisplay: React.FC<RequirementDisplayProps> = ({
|
|
229
229
|
const progress = user.progress[requirement.id];
|
230
230
|
|
231
231
|
const totalCount = requirement.counts[cohort] || requirement.counts[ALL_COHORTS];
|
232
|
-
const currentCount = progress
|
232
|
+
const currentCount = progress.counts[cohort] || progress.counts[ALL_COHORTS] || 0;
|
233
233
|
const isCompleted = currentCount >= totalCount;
|
234
234
|
|
235
235
|
let requirementName = requirement.name;
|
@@ -263,12 +263,12 @@ const RequirementDisplay: React.FC<RequirementDisplayProps> = ({
|
|
263
263
|
icon={<CheckIcon />}
|
264
264
|
label='Completed'
|
265
265
|
color='success'
|
266
|
-
onClick={() => setShowUpdateDialog(true)}
|
266
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
267
267
|
/>
|
268
268
|
) : (
|
269
269
|
<Button
|
270
270
|
variant='contained'
|
271
|
-
onClick={() => setShowUpdateDialog(true)}
|
271
|
+
onClick={() => { setShowUpdateDialog(true); }}
|
272
272
|
>
|
273
273
|
Update Progress
|
274
274
|
</Button>
|
@@ -303,8 +303,7 @@ const RequirementDisplay: React.FC<RequirementDisplayProps> = ({
|
|
303
303
|
</Grid>
|
304
304
|
)}
|
305
305
|
|
306
|
-
{requirement.videoUrls
|
307
|
-
requirement.videoUrls.map((url, idx) => (
|
306
|
+
{requirement.videoUrls?.map((url, idx) => (
|
308
307
|
<Box sx={{ mt: 3, width: 1, aspectRatio: '1.77' }} key={url}>
|
309
308
|
<iframe
|
310
309
|
src={url}
|
@@ -320,7 +319,7 @@ const RequirementDisplay: React.FC<RequirementDisplayProps> = ({
|
|
320
319
|
|
321
320
|
<ProgressDialog
|
322
321
|
open={showUpdateDialog}
|
323
|
-
onClose={() => setShowUpdateDialog(false)}
|
322
|
+
onClose={() => { setShowUpdateDialog(false); }}
|
324
323
|
requirement={requirement}
|
325
324
|
cohort={user.dojoCohort}
|
326
325
|
progress={progress}
|
@@ -9,9 +9,9 @@ import LoadingPage from '../loading/LoadingPage';
|
|
9
9
|
import NotFoundPage from '../NotFoundPage';
|
10
10
|
import RequirementDisplay from './RequirementDisplay';
|
11
11
|
|
12
|
-
|
12
|
+
interface RequirementPageProps {
|
13
13
|
id: string;
|
14
|
-
}
|
14
|
+
}
|
15
15
|
|
16
16
|
const RequirementPage = () => {
|
17
17
|
const { id } = useParams<RequirementPageProps>();
|
@@ -316,9 +316,9 @@ function getActionColumns(
|
|
316
316
|
</Tooltip>
|
317
317
|
}
|
318
318
|
onClick={() =>
|
319
|
-
setPinnedRowIds((prevPinnedRowIds) => {
|
319
|
+
{ setPinnedRowIds((prevPinnedRowIds) => {
|
320
320
|
return prevPinnedRowIds.filter((rowId) => rowId !== id);
|
321
|
-
})
|
321
|
+
}); }
|
322
322
|
}
|
323
323
|
/>,
|
324
324
|
];
|
@@ -332,7 +332,7 @@ function getActionColumns(
|
|
332
332
|
}
|
333
333
|
label='Pin Row'
|
334
334
|
onClick={() =>
|
335
|
-
setPinnedRowIds((prevPinnedRowIds) => [...prevPinnedRowIds, id])
|
335
|
+
{ setPinnedRowIds((prevPinnedRowIds) => [...prevPinnedRowIds, id]); }
|
336
336
|
}
|
337
337
|
/>,
|
338
338
|
];
|
@@ -26,7 +26,7 @@ const ScoreboardCheck: React.FC<ScoreboardCheckProps> = ({
|
|
26
26
|
const user = useAuth().user!;
|
27
27
|
|
28
28
|
const canUpdate = requirement && user.username === username;
|
29
|
-
const onClick = canUpdate ? () => setShowUpdateDialog(true) : undefined;
|
29
|
+
const onClick = canUpdate ? () => { setShowUpdateDialog(true); } : undefined;
|
30
30
|
|
31
31
|
return (
|
32
32
|
<>
|
@@ -43,7 +43,7 @@ const ScoreboardCheck: React.FC<ScoreboardCheckProps> = ({
|
|
43
43
|
{canUpdate && showUpdateDialog && (
|
44
44
|
<ProgressDialog
|
45
45
|
open={showUpdateDialog}
|
46
|
-
onClose={() => setShowUpdateDialog(false)}
|
46
|
+
onClose={() => { setShowUpdateDialog(false); }}
|
47
47
|
requirement={requirement}
|
48
48
|
cohort={cohort}
|
49
49
|
progress={user.progress[requirement.id]}
|
@@ -16,9 +16,9 @@ import { ScoreboardRow } from './scoreboardData';
|
|
16
16
|
import ScoreboardTutorial from './ScoreboardTutorial';
|
17
17
|
import ScoreboardViewSelector from './ScoreboardViewSelector';
|
18
18
|
|
19
|
-
|
19
|
+
interface ScoreboardPageParams {
|
20
20
|
type: string;
|
21
|
-
}
|
21
|
+
}
|
22
22
|
|
23
23
|
const ScoreboardPage = () => {
|
24
24
|
const user = useAuth().user!;
|
@@ -34,7 +34,7 @@ const ScoreboardProgress: React.FC<LinearProgressProps & ScoreboardProgressProps
|
|
34
34
|
const user = useAuth().user!;
|
35
35
|
|
36
36
|
const canUpdate = requirement && cohort && user.username === username;
|
37
|
-
const onClick = canUpdate ? () => setShowUpdateDialog(true) : undefined;
|
37
|
+
const onClick = canUpdate ? () => { setShowUpdateDialog(true); } : undefined;
|
38
38
|
|
39
39
|
const normalized = ((value - min) * 100) / (max - min);
|
40
40
|
const displayValue = Math.min(Math.max(normalized, 0), 100);
|
@@ -71,7 +71,7 @@ const ScoreboardProgress: React.FC<LinearProgressProps & ScoreboardProgressProps
|
|
71
71
|
{canUpdate && showUpdateDialog && (
|
72
72
|
<ProgressDialog
|
73
73
|
open={showUpdateDialog}
|
74
|
-
onClose={() => setShowUpdateDialog(false)}
|
74
|
+
onClose={() => { setShowUpdateDialog(false); }}
|
75
75
|
requirement={requirement}
|
76
76
|
cohort={cohort}
|
77
77
|
progress={user.progress[requirement.id]}
|
@@ -49,9 +49,9 @@ const ScoreboardViewSelector: React.FC<ScoreboardViewSelectorProps> = ({
|
|
49
49
|
label='View'
|
50
50
|
value={value}
|
51
51
|
onChange={(event) =>
|
52
|
-
onChange
|
52
|
+
{ onChange
|
53
53
|
? onChange(event.target.value)
|
54
|
-
: defaultOnChange(event.target.value)
|
54
|
+
: defaultOnChange(event.target.value); }
|
55
55
|
}
|
56
56
|
sx={{ mb: 3 }}
|
57
57
|
fullWidth
|
@@ -10,9 +10,9 @@ import LoadingPage from '../../loading/LoadingPage';
|
|
10
10
|
import Scoreboard from '../Scoreboard';
|
11
11
|
import ScoreboardViewSelector from '../ScoreboardViewSelector';
|
12
12
|
|
13
|
-
export
|
13
|
+
export interface ClubScoreboardPageParams {
|
14
14
|
id: string;
|
15
|
-
}
|
15
|
+
}
|
16
16
|
|
17
17
|
const ClubScoreboardPage = () => {
|
18
18
|
const { id } = useParams<ClubScoreboardPageParams>();
|
@@ -45,10 +45,10 @@ const Header: React.FC<HeaderProps> = ({ requirement, cohort }) => {
|
|
45
45
|
|
46
46
|
return (
|
47
47
|
<>
|
48
|
-
<div onClick={() => setShowReqModal(true)}>{headerName}</div>
|
48
|
+
<div onClick={() => { setShowReqModal(true); }}>{headerName}</div>
|
49
49
|
<RequirementModal
|
50
50
|
open={showReqModal}
|
51
|
-
onClose={() => setShowReqModal(false)}
|
51
|
+
onClose={() => { setShowReqModal(false); }}
|
52
52
|
requirement={requirement}
|
53
53
|
/>
|
54
54
|
</>
|
@@ -228,7 +228,7 @@ export function getRatingSystem(params: GridValueGetterParams<ScoreboardRow>) {
|
|
228
228
|
!isGraduation(params.row) &&
|
229
229
|
params.row.ratings[RatingSystem.Custom]?.name
|
230
230
|
) {
|
231
|
-
return `Custom (${params.row.ratings[RatingSystem.Custom]
|
231
|
+
return `Custom (${params.row.ratings[RatingSystem.Custom].name})`;
|
232
232
|
}
|
233
233
|
return formatRatingSystem(params.row.ratingSystem);
|
234
234
|
}
|
@@ -28,13 +28,13 @@ const AllColumns: GridColDef[] = [
|
|
28
28
|
{
|
29
29
|
field: 'dojoCohort',
|
30
30
|
headerName: 'Cohort',
|
31
|
-
valueGetter: (params: GridValueGetterParams<User
|
31
|
+
valueGetter: (params: GridValueGetterParams<User>) => params.row.dojoCohort,
|
32
32
|
minWidth: 125,
|
33
33
|
},
|
34
34
|
{
|
35
35
|
field: 'display',
|
36
36
|
headerName: 'Display Name',
|
37
|
-
valueGetter: (params: GridValueGetterParams<User
|
37
|
+
valueGetter: (params: GridValueGetterParams<User>) => params.row.displayName,
|
38
38
|
renderCell: (params: GridRenderCellParams<User, string>) => {
|
39
39
|
return (
|
40
40
|
<Stack direction='row' spacing={1} alignItems='center'>
|
@@ -55,14 +55,14 @@ const AllColumns: GridColDef[] = [
|
|
55
55
|
{
|
56
56
|
field: 'discord',
|
57
57
|
headerName: 'Discord Username',
|
58
|
-
valueGetter: (params: GridValueGetterParams<User
|
58
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
59
59
|
params.row.discordUsername,
|
60
60
|
flex: 1,
|
61
61
|
},
|
62
62
|
{
|
63
63
|
field: RatingSystem.Chesscom,
|
64
64
|
headerName: 'Chess.com Username',
|
65
|
-
valueGetter: (params: GridValueGetterParams<User
|
65
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
66
66
|
params.row.ratings[RatingSystem.Chesscom]?.username,
|
67
67
|
flex: 1,
|
68
68
|
minWidth: 175,
|
@@ -70,49 +70,49 @@ const AllColumns: GridColDef[] = [
|
|
70
70
|
{
|
71
71
|
field: RatingSystem.Lichess,
|
72
72
|
headerName: 'Lichess Username',
|
73
|
-
valueGetter: (params: GridValueGetterParams<User
|
73
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
74
74
|
params.row.ratings[RatingSystem.Lichess]?.username,
|
75
75
|
flex: 1,
|
76
76
|
},
|
77
77
|
{
|
78
78
|
field: RatingSystem.Fide,
|
79
79
|
headerName: 'FIDE ID',
|
80
|
-
valueGetter: (params: GridValueGetterParams<User
|
80
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
81
81
|
params.row.ratings[RatingSystem.Fide]?.username,
|
82
82
|
flex: 1,
|
83
83
|
},
|
84
84
|
{
|
85
85
|
field: RatingSystem.Uscf,
|
86
86
|
headerName: 'USCF ID',
|
87
|
-
valueGetter: (params: GridValueGetterParams<User
|
87
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
88
88
|
params.row.ratings[RatingSystem.Uscf]?.username,
|
89
89
|
flex: 1,
|
90
90
|
},
|
91
91
|
{
|
92
92
|
field: RatingSystem.Cfc,
|
93
93
|
headerName: 'CFC ID',
|
94
|
-
valueGetter: (params: GridValueGetterParams<User
|
94
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
95
95
|
params.row.ratings[RatingSystem.Cfc]?.username,
|
96
96
|
flex: 1,
|
97
97
|
},
|
98
98
|
{
|
99
99
|
field: RatingSystem.Ecf,
|
100
100
|
headerName: 'ECF ID',
|
101
|
-
valueGetter: (params: GridValueGetterParams<User
|
101
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
102
102
|
params.row.ratings[RatingSystem.Ecf]?.username,
|
103
103
|
flex: 1,
|
104
104
|
},
|
105
105
|
{
|
106
106
|
field: RatingSystem.Dwz,
|
107
107
|
headerName: 'DWZ ID',
|
108
|
-
valueGetter: (params: GridValueGetterParams<User
|
108
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
109
109
|
params.row.ratings[RatingSystem.Dwz]?.username,
|
110
110
|
flex: 1,
|
111
111
|
},
|
112
112
|
{
|
113
113
|
field: RatingSystem.Acf,
|
114
114
|
headerName: 'ACF ID',
|
115
|
-
valueGetter: (params: GridValueGetterParams<User
|
115
|
+
valueGetter: (params: GridValueGetterParams<User>) =>
|
116
116
|
params.row.ratings[RatingSystem.Acf]?.username,
|
117
117
|
flex: 1,
|
118
118
|
},
|
@@ -155,7 +155,7 @@ function useDebounce(
|
|
155
155
|
|
156
156
|
useEffect(() => {
|
157
157
|
const timeout = setTimeout(callback, delay);
|
158
|
-
return () => clearTimeout(timeout);
|
158
|
+
return () => { clearTimeout(timeout); };
|
159
159
|
}, [callback, delay]);
|
160
160
|
}
|
161
161
|
|
@@ -166,10 +166,10 @@ const SearchPage = () => {
|
|
166
166
|
const [query, setQuery] = useState('');
|
167
167
|
const [allFields, setAllFields] = useState(true);
|
168
168
|
const [fields, setFields] = useState<Record<string, boolean>>(
|
169
|
-
SearchFields.reduce((map, field) => {
|
169
|
+
SearchFields.reduce<Record<string, boolean>>((map, field) => {
|
170
170
|
map[field] = false;
|
171
171
|
return map;
|
172
|
-
}, {}
|
172
|
+
}, {})
|
173
173
|
);
|
174
174
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
175
175
|
const [columns, setColumns] = useState(AllColumns);
|
@@ -237,7 +237,7 @@ const SearchPage = () => {
|
|
237
237
|
data-cy='search-query'
|
238
238
|
label='Search Query'
|
239
239
|
value={query}
|
240
|
-
onChange={(e) => setQuery(e.target.value)}
|
240
|
+
onChange={(e) => { setQuery(e.target.value); }}
|
241
241
|
fullWidth
|
242
242
|
error={!!errors.query}
|
243
243
|
helperText={errors.query}
|
@@ -254,7 +254,7 @@ const SearchPage = () => {
|
|
254
254
|
<Checkbox
|
255
255
|
checked={allFields}
|
256
256
|
onChange={(event) =>
|
257
|
-
setAllFields(event.target.checked)
|
257
|
+
{ setAllFields(event.target.checked); }
|
258
258
|
}
|
259
259
|
/>
|
260
260
|
}
|
@@ -276,10 +276,10 @@ const SearchPage = () => {
|
|
276
276
|
<Checkbox
|
277
277
|
checked={allFields || fields[field]}
|
278
278
|
onChange={(event) =>
|
279
|
-
onChangeField(
|
279
|
+
{ onChangeField(
|
280
280
|
field,
|
281
281
|
event.target.checked
|
282
|
-
)
|
282
|
+
); }
|
283
283
|
}
|
284
284
|
/>
|
285
285
|
}
|
@@ -20,13 +20,13 @@ function stringToColor(string: string) {
|
|
20
20
|
return color;
|
21
21
|
}
|
22
22
|
|
23
|
-
export
|
23
|
+
export interface SxSize {
|
24
24
|
xs?: string;
|
25
25
|
sm?: string;
|
26
26
|
md?: string;
|
27
27
|
lg?: string;
|
28
28
|
xl?: string;
|
29
|
-
}
|
29
|
+
}
|
30
30
|
|
31
31
|
/**
|
32
32
|
* Returns the props for a MUI Avatar with the given size and name.
|
@@ -37,12 +37,12 @@ export type SxSize = {
|
|
37
37
|
export function avatarProps(name: string, size: number | SxSize = 74, fontSize?: SxSize) {
|
38
38
|
let uppercaseLetters = name.replace(/[a-z]/g, '').slice(0, 3);
|
39
39
|
|
40
|
-
|
40
|
+
const tokens = name.split(' ');
|
41
41
|
if (tokens.length > 1) {
|
42
42
|
uppercaseLetters = tokens
|
43
43
|
.slice(0, 3)
|
44
44
|
.map((v) => v[0])
|
45
|
-
.map((v) => v
|
45
|
+
.map((v) => v.toLocaleUpperCase() || '')
|
46
46
|
.join('');
|
47
47
|
}
|
48
48
|
|
@@ -59,7 +59,7 @@ const CompletedTacticsExamPgnSelector: React.FC<CompletedTacticsExamPgnSelectorP
|
|
59
59
|
<TextField
|
60
60
|
select
|
61
61
|
value={attempt}
|
62
|
-
onChange={(e) => selectAttempt(parseInt(e.target.value))}
|
62
|
+
onChange={(e) => { selectAttempt(parseInt(e.target.value)); }}
|
63
63
|
fullWidth
|
64
64
|
size='small'
|
65
65
|
sx={{ mb: 1 }}
|
@@ -84,7 +84,7 @@ const CompletedTacticsExamPgnSelector: React.FC<CompletedTacticsExamPgnSelectorP
|
|
84
84
|
<ListItem key={i} disablePadding>
|
85
85
|
<ListItemButton
|
86
86
|
selected={i === selected}
|
87
|
-
onClick={() => onSelect(i)}
|
87
|
+
onClick={() => { onSelect(i); }}
|
88
88
|
>
|
89
89
|
<ListItemIcon sx={{ minWidth: '40px' }}>
|
90
90
|
<Stack alignItems='center' width={1}>
|
@@ -61,7 +61,7 @@ const ExamStatistics: React.FC<ExamStatisticsProps> = ({ exam }) => {
|
|
61
61
|
valueFormatter: (value) => `Score: ${value.x}, Rating: ${value.y}`,
|
62
62
|
color: cohortColors[answer.cohort],
|
63
63
|
};
|
64
|
-
series.data
|
64
|
+
series.data.push({ x: answer.score, y: answer.rating, id: username });
|
65
65
|
cohortToSeries[answer.cohort] = series;
|
66
66
|
});
|
67
67
|
|
@@ -119,7 +119,7 @@ const ExamStatistics: React.FC<ExamStatisticsProps> = ({ exam }) => {
|
|
119
119
|
});
|
120
120
|
observer.observe(ref.current);
|
121
121
|
|
122
|
-
return () => observer.disconnect();
|
122
|
+
return () => { observer.disconnect(); };
|
123
123
|
}, [ref, series, setLegendMargin]);
|
124
124
|
|
125
125
|
const onChangeCohort = (newCohorts: string[]) => {
|
@@ -84,7 +84,7 @@ const TacticsExamPage = () => {
|
|
84
84
|
const [showRetakeDialog, setShowRetakeDialog] = useState(false);
|
85
85
|
const [showLatestAttempt, setShowLatestAttempt] = useState(false);
|
86
86
|
|
87
|
-
const hasTakenExam = Boolean(exam
|
87
|
+
const hasTakenExam = Boolean(exam.answers[user.username]);
|
88
88
|
|
89
89
|
useEffect(() => {
|
90
90
|
if (!answerRequest.isSent() && exam && hasTakenExam) {
|
@@ -128,13 +128,13 @@ const TacticsExamPage = () => {
|
|
128
128
|
<CompletedTacticsExam
|
129
129
|
exam={exam}
|
130
130
|
answerRequest={answerRequest}
|
131
|
-
onReset={() => setShowRetakeDialog(true)}
|
131
|
+
onReset={() => { setShowRetakeDialog(true); }}
|
132
132
|
resetLabel='Retake Test'
|
133
133
|
showLatestAttempt={showLatestAttempt}
|
134
134
|
/>
|
135
135
|
<Dialog
|
136
136
|
open={showRetakeDialog}
|
137
|
-
onClose={() => setShowRetakeDialog(false)}
|
137
|
+
onClose={() => { setShowRetakeDialog(false); }}
|
138
138
|
>
|
139
139
|
<DialogTitle>Retake this test?</DialogTitle>
|
140
140
|
<DialogContent>
|
@@ -145,7 +145,7 @@ const TacticsExamPage = () => {
|
|
145
145
|
</DialogContentText>
|
146
146
|
</DialogContent>
|
147
147
|
<DialogActions>
|
148
|
-
<Button onClick={() => setShowRetakeDialog(false)}>Cancel</Button>
|
148
|
+
<Button onClick={() => { setShowRetakeDialog(false); }}>Cancel</Button>
|
149
149
|
<Button onClick={onRetake}>Retake</Button>
|
150
150
|
</DialogActions>
|
151
151
|
</Dialog>
|
@@ -186,7 +186,7 @@ export const InProgressTacticsExam: React.FC<InProgressTacticsExamProps> = ({
|
|
186
186
|
const api = useApi();
|
187
187
|
const pgnApi = useRef<PgnBoardApi>(null);
|
188
188
|
const [selectedProblem, setSelectedProblem] = useState(0);
|
189
|
-
const answerPgns = useRef<string[]>((exam
|
189
|
+
const answerPgns = useRef<string[]>((exam.pgns || []).map(() => ''));
|
190
190
|
const [isTimeOver, setIsTimeOver] = useState(false);
|
191
191
|
const [problemStatus, setProblemStatus] = useState<Record<number, ProblemStatus>>({});
|
192
192
|
|
@@ -198,9 +198,9 @@ export const InProgressTacticsExam: React.FC<InProgressTacticsExamProps> = ({
|
|
198
198
|
isPlaying: !disableClock,
|
199
199
|
size: 80,
|
200
200
|
strokeWidth: 6,
|
201
|
-
duration: exam
|
201
|
+
duration: exam.timeLimitSeconds || 3600,
|
202
202
|
colors: ['#66bb6a', '#29b6f6', '#ce93d8', '#ffa726', '#f44336'],
|
203
|
-
colorsTime: getColorsTime(exam
|
203
|
+
colorsTime: getColorsTime(exam.timeLimitSeconds),
|
204
204
|
trailColor: 'rgba(0,0,0,0)',
|
205
205
|
onComplete: onCountdownComplete,
|
206
206
|
});
|
@@ -93,7 +93,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
93
93
|
justifyContent='center'
|
94
94
|
>
|
95
95
|
<CountdownTimer {...countdown} />
|
96
|
-
<Button variant='contained' onClick={() => setIsFinishEarly(true)}>
|
96
|
+
<Button variant='contained' onClick={() => { setIsFinishEarly(true); }}>
|
97
97
|
Finish Early
|
98
98
|
</Button>
|
99
99
|
</Stack>
|
@@ -103,8 +103,8 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
103
103
|
<ListItem key={i} disablePadding>
|
104
104
|
<ListItemButton
|
105
105
|
selected={i === selected}
|
106
|
-
onClick={() => onSelect(i)}
|
107
|
-
onContextMenu={(e) => handleOpenStatusMenu(i, e)}
|
106
|
+
onClick={() => { onSelect(i); }}
|
107
|
+
onContextMenu={(e) => { handleOpenStatusMenu(i, e); }}
|
108
108
|
>
|
109
109
|
<ListItemIcon sx={{ minWidth: '40px' }}>
|
110
110
|
<Stack alignItems='center' width={1}>
|
@@ -151,7 +151,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
151
151
|
|
152
152
|
<Dialog
|
153
153
|
open={isFinishEarly}
|
154
|
-
onClose={() => setIsFinishEarly(false)}
|
154
|
+
onClose={() => { setIsFinishEarly(false); }}
|
155
155
|
classes={{
|
156
156
|
container: BlockBoardKeyboardShortcuts,
|
157
157
|
}}
|
@@ -166,7 +166,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
166
166
|
</DialogContentText>
|
167
167
|
</DialogContent>
|
168
168
|
<DialogActions>
|
169
|
-
<Button onClick={() => setIsFinishEarly(false)}>Cancel</Button>
|
169
|
+
<Button onClick={() => { setIsFinishEarly(false); }}>Cancel</Button>
|
170
170
|
<Button onClick={onComplete}>Finish</Button>
|
171
171
|
</DialogActions>
|
172
172
|
</Dialog>
|
@@ -185,7 +185,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
185
185
|
}}
|
186
186
|
>
|
187
187
|
<MenuItem
|
188
|
-
onClick={() => markStatus(ProblemStatus.Complete)}
|
188
|
+
onClick={() => { markStatus(ProblemStatus.Complete); }}
|
189
189
|
disabled={
|
190
190
|
problemStatus?.[openStatusProblem] === ProblemStatus.Complete
|
191
191
|
}
|
@@ -193,7 +193,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
193
193
|
Mark as Completed
|
194
194
|
</MenuItem>
|
195
195
|
<MenuItem
|
196
|
-
onClick={() => markStatus(ProblemStatus.NeedsReview)}
|
196
|
+
onClick={() => { markStatus(ProblemStatus.NeedsReview); }}
|
197
197
|
disabled={
|
198
198
|
problemStatus?.[openStatusProblem] === ProblemStatus.NeedsReview
|
199
199
|
}
|
@@ -201,7 +201,7 @@ const TacticsExamPgnSelector: React.FC<TacticsExamPgnSelectorProps> = ({
|
|
201
201
|
Mark as Needs Review
|
202
202
|
</MenuItem>
|
203
203
|
<MenuItem
|
204
|
-
onClick={() => markStatus(ProblemStatus.Unknown)}
|
204
|
+
onClick={() => { markStatus(ProblemStatus.Unknown); }}
|
205
205
|
disabled={!problemStatus?.[openStatusProblem]}
|
206
206
|
>
|
207
207
|
Clear Status
|
@@ -14,7 +14,7 @@ const TacticsInstructionsPage = () => {
|
|
14
14
|
navigate('/tactics/exam', { state: locationState });
|
15
15
|
};
|
16
16
|
|
17
|
-
if (!locationState
|
17
|
+
if (!locationState?.exam) {
|
18
18
|
return <Navigate to='/tactics/' />;
|
19
19
|
}
|
20
20
|
|
@@ -148,7 +148,7 @@ const ExamsTable = ({ exams }: { exams: Exam[] }) => {
|
|
148
148
|
if (
|
149
149
|
!hasAnswered &&
|
150
150
|
i >= 1 &&
|
151
|
-
!
|
151
|
+
!exams[i - 1].answers[user?.username || '']
|
152
152
|
) {
|
153
153
|
return (
|
154
154
|
<Tooltip title='This exam is locked until you complete the previous exam'>
|
@@ -255,7 +255,7 @@ const ExamsTable = ({ exams }: { exams: Exam[] }) => {
|
|
255
255
|
}
|
256
256
|
|
257
257
|
const i = exams.findIndex((e) => e.id === params.row.id);
|
258
|
-
if (i >= 1 && !
|
258
|
+
if (i >= 1 && !exams[i - 1].answers[user?.username || '']) {
|
259
259
|
setSnackbarOpen(true);
|
260
260
|
} else if (i >= 1 && isFreeTier) {
|
261
261
|
setUpsellOpen(true);
|
@@ -296,7 +296,7 @@ const ExamsTable = ({ exams }: { exams: Exam[] }) => {
|
|
296
296
|
</Snackbar>
|
297
297
|
<UpsellDialog
|
298
298
|
open={upsellOpen}
|
299
|
-
onClose={() => setUpsellOpen(false)}
|
299
|
+
onClose={() => { setUpsellOpen(false); }}
|
300
300
|
currentAction={RestrictedAction.TacticsExams}
|
301
301
|
/>
|
302
302
|
</>
|
@@ -113,7 +113,7 @@ const ExamListSection: React.FC<ExamListSectionProps> = ({
|
|
113
113
|
<Stack>
|
114
114
|
<Stack spacing={1} direction='row' alignItems='center'>
|
115
115
|
<Tooltip title={expanded ? 'Collapse Section' : 'Expand Section'}>
|
116
|
-
<IconButton onClick={() => setExpanded(!expanded)}>
|
116
|
+
<IconButton onClick={() => { setExpanded(!expanded); }}>
|
117
117
|
{expanded ? <ExpandLess /> : <ExpandMore />}
|
118
118
|
</IconButton>
|
119
119
|
</Tooltip>
|
@@ -113,10 +113,10 @@ export function getSolutionScore(
|
|
113
113
|
): number {
|
114
114
|
let score = 0;
|
115
115
|
|
116
|
-
for (
|
116
|
+
for (const move of solution) {
|
117
117
|
// Recursively check variations
|
118
118
|
if (move.variations.length > 0) {
|
119
|
-
for (
|
119
|
+
for (const variation of move.variations) {
|
120
120
|
score += getSolutionScore(playAs, variation, chess, isUnscored);
|
121
121
|
}
|
122
122
|
}
|
@@ -182,12 +182,12 @@ export function scoreVariation(
|
|
182
182
|
let score = 0;
|
183
183
|
let altFound = false;
|
184
184
|
|
185
|
-
for (
|
185
|
+
for (const move of solution) {
|
186
186
|
// The user may not have found the mainline solution,
|
187
187
|
// but may have found a variation, which can also have a score associated, or can be an alternate solution
|
188
188
|
// for this move
|
189
189
|
if (move.variations.length > 0) {
|
190
|
-
for (
|
190
|
+
for (const variation of move.variations) {
|
191
191
|
const [variationScore, alt] = scoreVariation(
|
192
192
|
playAs,
|
193
193
|
variation,
|
@@ -248,9 +248,9 @@ export function addExtraVariation(
|
|
248
248
|
currentSolutionMove: Move | null,
|
249
249
|
solution: Chess,
|
250
250
|
) {
|
251
|
-
for (
|
251
|
+
for (const move of answer) {
|
252
252
|
if (move.variations.length > 0) {
|
253
|
-
for (
|
253
|
+
for (const variation of move.variations) {
|
254
254
|
addExtraVariation(variation, currentSolutionMove, solution);
|
255
255
|
}
|
256
256
|
}
|
@@ -102,7 +102,7 @@ const LeaderboardTab = () => {
|
|
102
102
|
select
|
103
103
|
label='Site'
|
104
104
|
value={site}
|
105
|
-
onChange={(e) => setSite(e.target.value as LeaderboardSite)}
|
105
|
+
onChange={(e) => { setSite(e.target.value as LeaderboardSite); }}
|
106
106
|
>
|
107
107
|
<MenuItem value={LeaderboardSite.Lichess}>Lichess</MenuItem>
|
108
108
|
<MenuItem value={LeaderboardSite.Chesscom}>Chess.com</MenuItem>
|
@@ -114,7 +114,7 @@ const LeaderboardTab = () => {
|
|
114
114
|
select
|
115
115
|
label='Time Control'
|
116
116
|
value={timeControl}
|
117
|
-
onChange={(e) => setTimeControl(e.target.value as TimeControl)}
|
117
|
+
onChange={(e) => { setTimeControl(e.target.value as TimeControl); }}
|
118
118
|
>
|
119
119
|
<MenuItem value={'blitz'}>Blitz</MenuItem>
|
120
120
|
<MenuItem value={'rapid'}>Rapid</MenuItem>
|
@@ -128,7 +128,7 @@ const LeaderboardTab = () => {
|
|
128
128
|
label='Tournament Type'
|
129
129
|
value={tournamentType}
|
130
130
|
onChange={(e) =>
|
131
|
-
setTournamentType(e.target.value as TournamentType)
|
131
|
+
{ setTournamentType(e.target.value as TournamentType); }
|
132
132
|
}
|
133
133
|
>
|
134
134
|
<MenuItem value={TournamentType.Arena}>Arena</MenuItem>
|
@@ -159,13 +159,13 @@ const LeaderboardTab = () => {
|
|
159
159
|
|
160
160
|
<Button
|
161
161
|
color={timePeriod === 'monthly' ? 'primary' : 'inherit'}
|
162
|
-
onClick={() => setTimePeriod('monthly')}
|
162
|
+
onClick={() => { setTimePeriod('monthly'); }}
|
163
163
|
>
|
164
164
|
Monthly
|
165
165
|
</Button>
|
166
166
|
<Button
|
167
167
|
color={timePeriod === 'yearly' ? 'primary' : 'inherit'}
|
168
|
-
onClick={() => setTimePeriod('yearly')}
|
168
|
+
onClick={() => { setTimePeriod('yearly'); }}
|
169
169
|
>
|
170
170
|
Yearly
|
171
171
|
</Button>
|
@@ -106,10 +106,10 @@ export const TournamentCalendarFilters: React.FC<TournamentCalendarFiltersProps>
|
|
106
106
|
<Checkbox
|
107
107
|
checked={filters.tournamentTypes[type]}
|
108
108
|
onChange={(event) =>
|
109
|
-
onChangeTournamentType(
|
109
|
+
{ onChangeTournamentType(
|
110
110
|
type,
|
111
111
|
event.target.checked,
|
112
|
-
)
|
112
|
+
); }
|
113
113
|
}
|
114
114
|
/>
|
115
115
|
}
|
@@ -138,10 +138,10 @@ export const TournamentCalendarFilters: React.FC<TournamentCalendarFiltersProps>
|
|
138
138
|
<Checkbox
|
139
139
|
checked={filters.tournamentTimeControls[type]}
|
140
140
|
onChange={(event) =>
|
141
|
-
onChangeTournamentTimeControl(
|
141
|
+
{ onChangeTournamentTimeControl(
|
142
142
|
type,
|
143
143
|
event.target.checked,
|
144
|
-
)
|
144
|
+
); }
|
145
145
|
}
|
146
146
|
color={getColor(type)}
|
147
147
|
/>
|
@@ -171,10 +171,10 @@ export const TournamentCalendarFilters: React.FC<TournamentCalendarFiltersProps>
|
|
171
171
|
<Checkbox
|
172
172
|
checked={filters.tournamentPositions[type]}
|
173
173
|
onChange={(event) =>
|
174
|
-
onChangeTournamentPositions(
|
174
|
+
{ onChangeTournamentPositions(
|
175
175
|
type,
|
176
176
|
event.target.checked,
|
177
|
-
)
|
177
|
+
); }
|
178
178
|
}
|
179
179
|
/>
|
180
180
|
}
|
@@ -17,7 +17,7 @@ const TournamentsPage = () => {
|
|
17
17
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
18
18
|
<TabList
|
19
19
|
data-cy='tournaments-tab-list'
|
20
|
-
onChange={(_, t) => setSearchParams({ type: t })}
|
20
|
+
onChange={(_, t) => { setSearchParams({ type: t }); }}
|
21
21
|
variant='scrollable'
|
22
22
|
>
|
23
23
|
<Tab label='Calendar' value='calendar' />
|
@@ -81,7 +81,7 @@ const DetailsPage = () => {
|
|
81
81
|
</Stack>
|
82
82
|
|
83
83
|
{(user?.isAdmin || user?.isTournamentAdmin) && (
|
84
|
-
<Button variant='contained' onClick={() => navigate('./admin')}>
|
84
|
+
<Button variant='contained' onClick={() => { navigate('./admin'); }}>
|
85
85
|
Admin Portal
|
86
86
|
</Button>
|
87
87
|
)}
|
@@ -113,7 +113,7 @@ const Details: React.FC<DetailsProps> = ({ openClassical }) => {
|
|
113
113
|
const view = searchParams.get('view') || 'standings';
|
114
114
|
|
115
115
|
const maxRound =
|
116
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
116
|
+
openClassical.sections[`${region}_${ratingRange}`].rounds.length || 0;
|
117
117
|
|
118
118
|
const updateSearchParams = (key: string, value: string) => {
|
119
119
|
const updatedParams = new URLSearchParams(searchParams.toString());
|
@@ -167,7 +167,7 @@ const Details: React.FC<DetailsProps> = ({ openClassical }) => {
|
|
167
167
|
label='Region'
|
168
168
|
select
|
169
169
|
value={region}
|
170
|
-
onChange={(e) => updateSearchParams('region', e.target.value)}
|
170
|
+
onChange={(e) => { updateSearchParams('region', e.target.value); }}
|
171
171
|
sx={{
|
172
172
|
flexGrow: 1,
|
173
173
|
}}
|
@@ -181,7 +181,7 @@ const Details: React.FC<DetailsProps> = ({ openClassical }) => {
|
|
181
181
|
label='Section'
|
182
182
|
select
|
183
183
|
value={ratingRange}
|
184
|
-
onChange={(e) => updateSearchParams('ratingRange', e.target.value)}
|
184
|
+
onChange={(e) => { updateSearchParams('ratingRange', e.target.value); }}
|
185
185
|
sx={{
|
186
186
|
flexGrow: 1,
|
187
187
|
}}
|
@@ -195,7 +195,7 @@ const Details: React.FC<DetailsProps> = ({ openClassical }) => {
|
|
195
195
|
label='View'
|
196
196
|
select
|
197
197
|
value={view}
|
198
|
-
onChange={(e) => updateSearchParams('view', e.target.value)}
|
198
|
+
onChange={(e) => { updateSearchParams('view', e.target.value); }}
|
199
199
|
sx={{
|
200
200
|
flexGrow: 1,
|
201
201
|
}}
|
@@ -94,7 +94,7 @@ const PairingsTable: React.FC<PairingsTableProps> = ({
|
|
94
94
|
round,
|
95
95
|
}) => {
|
96
96
|
const pairings =
|
97
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
97
|
+
openClassical.sections[`${region}_${ratingRange}`].rounds[round - 1]?.pairings ??
|
98
98
|
[];
|
99
99
|
|
100
100
|
return (
|
@@ -33,7 +33,7 @@ const RegistrationPage = () => {
|
|
33
33
|
|
34
34
|
const [email, setEmail] = useState('');
|
35
35
|
const [lichessUsername, setLichessUsername] = useState(
|
36
|
-
user?.ratings
|
36
|
+
user?.ratings.LICHESS?.username || '',
|
37
37
|
);
|
38
38
|
const [discordUsername, setDiscordUsername] = useState(user?.discordUsername || '');
|
39
39
|
const [title, setTitle] = useState('');
|
@@ -53,7 +53,7 @@ const RegistrationPage = () => {
|
|
53
53
|
const request = useRequest();
|
54
54
|
|
55
55
|
useEffect(() => {
|
56
|
-
setLichessUsername(user?.ratings
|
56
|
+
setLichessUsername(user?.ratings.LICHESS?.username || '');
|
57
57
|
setDiscordUsername(user?.discordUsername || '');
|
58
58
|
}, [user]);
|
59
59
|
|
@@ -130,7 +130,7 @@ const RegistrationPage = () => {
|
|
130
130
|
<TextField
|
131
131
|
label='Email'
|
132
132
|
value={email}
|
133
|
-
onChange={(e) => setEmail(e.target.value)}
|
133
|
+
onChange={(e) => { setEmail(e.target.value); }}
|
134
134
|
required
|
135
135
|
fullWidth
|
136
136
|
error={Boolean(errors.email)}
|
@@ -141,9 +141,9 @@ const RegistrationPage = () => {
|
|
141
141
|
<TextField
|
142
142
|
label='Lichess Username'
|
143
143
|
value={lichessUsername}
|
144
|
-
onChange={(e) => setLichessUsername(e.target.value)}
|
145
|
-
disabled={Boolean(user?.ratings
|
146
|
-
required={!
|
144
|
+
onChange={(e) => { setLichessUsername(e.target.value); }}
|
145
|
+
disabled={Boolean(user?.ratings.LICHESS?.username)}
|
146
|
+
required={!user?.ratings.LICHESS?.username}
|
147
147
|
fullWidth
|
148
148
|
error={Boolean(errors.lichessUsername)}
|
149
149
|
helperText={errors.lichessUsername}
|
@@ -152,9 +152,9 @@ const RegistrationPage = () => {
|
|
152
152
|
<TextField
|
153
153
|
label='Discord Username'
|
154
154
|
value={discordUsername}
|
155
|
-
onChange={(e) => setDiscordUsername(e.target.value)}
|
155
|
+
onChange={(e) => { setDiscordUsername(e.target.value); }}
|
156
156
|
disabled={Boolean(user?.discordUsername)}
|
157
|
-
required={!
|
157
|
+
required={!user?.discordUsername}
|
158
158
|
fullWidth
|
159
159
|
error={Boolean(errors.discordUsername)}
|
160
160
|
helperText={errors.discordUsername}
|
@@ -163,7 +163,7 @@ const RegistrationPage = () => {
|
|
163
163
|
<TextField
|
164
164
|
label='Title'
|
165
165
|
value={title}
|
166
|
-
onChange={(e) => setTitle(e.target.value)}
|
166
|
+
onChange={(e) => { setTitle(e.target.value); }}
|
167
167
|
select
|
168
168
|
fullWidth
|
169
169
|
>
|
@@ -184,7 +184,7 @@ const RegistrationPage = () => {
|
|
184
184
|
select
|
185
185
|
required
|
186
186
|
value={region}
|
187
|
-
onChange={(e) => setRegion(e.target.value)}
|
187
|
+
onChange={(e) => { setRegion(e.target.value); }}
|
188
188
|
error={Boolean(errors.region)}
|
189
189
|
helperText={errors.region}
|
190
190
|
fullWidth
|
@@ -199,7 +199,7 @@ const RegistrationPage = () => {
|
|
199
199
|
select
|
200
200
|
required
|
201
201
|
value={section}
|
202
|
-
onChange={(e) => setSection(e.target.value)}
|
202
|
+
onChange={(e) => { setSection(e.target.value); }}
|
203
203
|
error={Boolean(errors.section)}
|
204
204
|
helperText={errors.section}
|
205
205
|
fullWidth
|
@@ -218,7 +218,7 @@ const RegistrationPage = () => {
|
|
218
218
|
<Checkbox
|
219
219
|
checked={byeRequests[i]}
|
220
220
|
onChange={(event) =>
|
221
|
-
onSetByeRequest(i, event.target.checked)
|
221
|
+
{ onSetByeRequest(i, event.target.checked); }
|
222
222
|
}
|
223
223
|
/>
|
224
224
|
}
|
@@ -281,9 +281,9 @@ const RegistrationPage = () => {
|
|
281
281
|
<DialogActions>
|
282
282
|
<Button
|
283
283
|
onClick={() =>
|
284
|
-
navigate(
|
284
|
+
{ navigate(
|
285
285
|
`/tournaments/open-classical?region=${region}&ratingRange=${section}`,
|
286
|
-
)
|
286
|
+
); }
|
287
287
|
}
|
288
288
|
>
|
289
289
|
Done
|
@@ -128,7 +128,7 @@ const SubmitResultsPage = () => {
|
|
128
128
|
console.log('submitResultsForOpenClassical: ', resp);
|
129
129
|
request.onSuccess();
|
130
130
|
const round =
|
131
|
-
resp.data.sections
|
131
|
+
resp.data.sections[`${region}_${section}`].rounds.length ||
|
132
132
|
'standings';
|
133
133
|
navigate(
|
134
134
|
`/tournaments/open-classical?region=${region}&ratingRange=${section}&view=${round}`,
|
@@ -161,7 +161,7 @@ const SubmitResultsPage = () => {
|
|
161
161
|
label='Email'
|
162
162
|
required
|
163
163
|
value={email}
|
164
|
-
onChange={(e) => setEmail(e.target.value)}
|
164
|
+
onChange={(e) => { setEmail(e.target.value); }}
|
165
165
|
error={Boolean(errors.email)}
|
166
166
|
helperText={
|
167
167
|
errors.email ||
|
@@ -176,7 +176,7 @@ const SubmitResultsPage = () => {
|
|
176
176
|
select
|
177
177
|
required
|
178
178
|
value={region}
|
179
|
-
onChange={(e) => setRegion(e.target.value)}
|
179
|
+
onChange={(e) => { setRegion(e.target.value); }}
|
180
180
|
error={Boolean(errors.region)}
|
181
181
|
helperText={errors.region}
|
182
182
|
>
|
@@ -190,7 +190,7 @@ const SubmitResultsPage = () => {
|
|
190
190
|
select
|
191
191
|
required
|
192
192
|
value={section}
|
193
|
-
onChange={(e) => setSection(e.target.value)}
|
193
|
+
onChange={(e) => { setSection(e.target.value); }}
|
194
194
|
error={Boolean(errors.section)}
|
195
195
|
helperText={errors.section}
|
196
196
|
>
|
@@ -202,7 +202,7 @@ const SubmitResultsPage = () => {
|
|
202
202
|
data-cy='game-url'
|
203
203
|
label='Game URL'
|
204
204
|
value={gameUrl}
|
205
|
-
onChange={(e) => setGameUrl(e.target.value)}
|
205
|
+
onChange={(e) => { setGameUrl(e.target.value); }}
|
206
206
|
onBlur={onBlurGameUrl}
|
207
207
|
error={Boolean(errors.gameUrl)}
|
208
208
|
helperText={errors.gameUrl || 'Please provide a link to the game'}
|
@@ -213,7 +213,7 @@ const SubmitResultsPage = () => {
|
|
213
213
|
label='White'
|
214
214
|
required
|
215
215
|
value={white}
|
216
|
-
onChange={(e) => setWhite(e.target.value)}
|
216
|
+
onChange={(e) => { setWhite(e.target.value); }}
|
217
217
|
error={Boolean(errors.white)}
|
218
218
|
helperText={
|
219
219
|
errors.white ||
|
@@ -225,7 +225,7 @@ const SubmitResultsPage = () => {
|
|
225
225
|
label='Black'
|
226
226
|
required
|
227
227
|
value={black}
|
228
|
-
onChange={(e) => setBlack(e.target.value)}
|
228
|
+
onChange={(e) => { setBlack(e.target.value); }}
|
229
229
|
error={Boolean(errors.black)}
|
230
230
|
helperText={
|
231
231
|
errors.black ||
|
@@ -239,7 +239,7 @@ const SubmitResultsPage = () => {
|
|
239
239
|
select
|
240
240
|
required
|
241
241
|
value={result}
|
242
|
-
onChange={(e) => setResult(e.target.value)}
|
242
|
+
onChange={(e) => { setResult(e.target.value); }}
|
243
243
|
error={Boolean(errors.result)}
|
244
244
|
helperText={errors.result}
|
245
245
|
>
|
@@ -258,7 +258,7 @@ const SubmitResultsPage = () => {
|
|
258
258
|
<Checkbox
|
259
259
|
checked={reportOpponent}
|
260
260
|
onChange={(event) =>
|
261
|
-
setReportOpponent(event.target.checked)
|
261
|
+
{ setReportOpponent(event.target.checked); }
|
262
262
|
}
|
263
263
|
/>
|
264
264
|
}
|
@@ -272,7 +272,7 @@ const SubmitResultsPage = () => {
|
|
272
272
|
multiline
|
273
273
|
minRows={3}
|
274
274
|
value={notes}
|
275
|
-
onChange={(e) => setNotes(e.target.value)}
|
275
|
+
onChange={(e) => { setNotes(e.target.value); }}
|
276
276
|
/>
|
277
277
|
|
278
278
|
<LoadingButton
|
@@ -64,7 +64,7 @@ const AdminPage = () => {
|
|
64
64
|
{request.data && (
|
65
65
|
<TabContext value={tab}>
|
66
66
|
<TabList
|
67
|
-
onChange={(_, value) => setTab(value)}
|
67
|
+
onChange={(_, value) => { setTab(value); }}
|
68
68
|
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
69
69
|
>
|
70
70
|
<Tab label='Active Players' value='players' />
|
@@ -42,7 +42,7 @@ const BannedPlayersTab: React.FC<BannedPlayersTabProps> = ({
|
|
42
42
|
<GridActionsCellItem
|
43
43
|
icon={<Check color='success' />}
|
44
44
|
label='Unban Player'
|
45
|
-
onClick={() => setUnbanPlayer(params.row.lichessUsername)}
|
45
|
+
onClick={() => { setUnbanPlayer(params.row.lichessUsername); }}
|
46
46
|
/>
|
47
47
|
</Tooltip>,
|
48
48
|
],
|
@@ -90,7 +90,7 @@ const BannedPlayersTab: React.FC<BannedPlayersTabProps> = ({
|
|
90
90
|
|
91
91
|
<Dialog
|
92
92
|
open={Boolean(unbanPlayer)}
|
93
|
-
onClose={unbanRequest.isLoading() ? undefined : () => setUnbanPlayer('')}
|
93
|
+
onClose={unbanRequest.isLoading() ? undefined : () => { setUnbanPlayer(''); }}
|
94
94
|
maxWidth='sm'
|
95
95
|
fullWidth
|
96
96
|
>
|
@@ -103,7 +103,7 @@ const BannedPlayersTab: React.FC<BannedPlayersTabProps> = ({
|
|
103
103
|
</DialogContent>
|
104
104
|
<DialogActions>
|
105
105
|
<Button
|
106
|
-
onClick={() => setUnbanPlayer('')}
|
106
|
+
onClick={() => { setUnbanPlayer(''); }}
|
107
107
|
disabled={unbanRequest.isLoading()}
|
108
108
|
>
|
109
109
|
Cancel
|
@@ -30,7 +30,7 @@ const CompleteTournament: React.FC<CompleteTournamentProps> = ({
|
|
30
30
|
const api = useApi();
|
31
31
|
|
32
32
|
const onComplete = () => {
|
33
|
-
if (!date
|
33
|
+
if (!date?.isValid) {
|
34
34
|
return;
|
35
35
|
}
|
36
36
|
|
@@ -54,12 +54,12 @@ const CompleteTournament: React.FC<CompleteTournamentProps> = ({
|
|
54
54
|
|
55
55
|
return (
|
56
56
|
<>
|
57
|
-
<Button variant='contained' color='error' onClick={() => setOpen(true)}>
|
57
|
+
<Button variant='contained' color='error' onClick={() => { setOpen(true); }}>
|
58
58
|
Complete Tournament
|
59
59
|
</Button>
|
60
60
|
<Dialog
|
61
61
|
open={open}
|
62
|
-
onClose={request.isLoading() ? undefined : () => setOpen(false)}
|
62
|
+
onClose={request.isLoading() ? undefined : () => { setOpen(false); }}
|
63
63
|
maxWidth='sm'
|
64
64
|
fullWidth
|
65
65
|
>
|
@@ -75,12 +75,12 @@ const CompleteTournament: React.FC<CompleteTournamentProps> = ({
|
|
75
75
|
<DatePicker
|
76
76
|
label='Next Tournament Start Date'
|
77
77
|
value={date}
|
78
|
-
onChange={(newValue) => setDate(newValue)}
|
78
|
+
onChange={(newValue) => { setDate(newValue); }}
|
79
79
|
/>
|
80
80
|
</Stack>
|
81
81
|
</DialogContent>
|
82
82
|
<DialogActions>
|
83
|
-
<Button onClick={() => setOpen(false)} disabled={request.isLoading()}>
|
83
|
+
<Button onClick={() => { setOpen(false); }} disabled={request.isLoading()}>
|
84
84
|
Cancel
|
85
85
|
</Button>
|
86
86
|
<LoadingButton
|
@@ -102,7 +102,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
102
102
|
if (openClassical.acceptingRegistrations) {
|
103
103
|
return (
|
104
104
|
<>
|
105
|
-
<Button variant='contained' onClick={() => setOpen(true)}>
|
105
|
+
<Button variant='contained' onClick={() => { setOpen(true); }}>
|
106
106
|
Edit Pairings
|
107
107
|
</Button>
|
108
108
|
<Dialog open={open} onClose={handleClose} maxWidth='sm' fullWidth>
|
@@ -129,7 +129,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
129
129
|
|
130
130
|
return (
|
131
131
|
<>
|
132
|
-
<Button variant='contained' onClick={() => setOpen(true)}>
|
132
|
+
<Button variant='contained' onClick={() => { setOpen(true); }}>
|
133
133
|
Edit Pairings
|
134
134
|
</Button>
|
135
135
|
<Dialog open={open} onClose={handleClose} maxWidth='sm' fullWidth>
|
@@ -142,7 +142,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
142
142
|
select
|
143
143
|
required
|
144
144
|
value={region}
|
145
|
-
onChange={(e) => setRegion(e.target.value)}
|
145
|
+
onChange={(e) => { setRegion(e.target.value); }}
|
146
146
|
error={Boolean(errors.region)}
|
147
147
|
helperText={errors.region}
|
148
148
|
>
|
@@ -158,7 +158,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
158
158
|
select
|
159
159
|
required
|
160
160
|
value={section}
|
161
|
-
onChange={(e) => setSection(e.target.value)}
|
161
|
+
onChange={(e) => { setSection(e.target.value); }}
|
162
162
|
error={Boolean(errors.section)}
|
163
163
|
helperText={errors.section}
|
164
164
|
>
|
@@ -171,7 +171,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
171
171
|
label='Round'
|
172
172
|
fullWidth
|
173
173
|
value={round}
|
174
|
-
onChange={(e) => setRound(parseInt(e.target.value))}
|
174
|
+
onChange={(e) => { setRound(parseInt(e.target.value)); }}
|
175
175
|
error={!!errors.round}
|
176
176
|
helperText={errors.round}
|
177
177
|
>
|
@@ -190,7 +190,7 @@ const Editor: React.FC<EditorProps> = ({ openClassical, onSuccess }) => {
|
|
190
190
|
minRows={3}
|
191
191
|
maxRows={15}
|
192
192
|
value={csvData}
|
193
|
-
onChange={(e) => setCsvData(e.target.value)}
|
193
|
+
onChange={(e) => { setCsvData(e.target.value); }}
|
194
194
|
error={!!errors.csvData}
|
195
195
|
helperText={errors.csvData}
|
196
196
|
/>
|
@@ -58,7 +58,7 @@ const EmailPairingsButton: React.FC<EmailPairingsButtonProps> = ({
|
|
58
58
|
variant='contained'
|
59
59
|
color='warning'
|
60
60
|
disabled={emailsSent || maxRound !== currentRound}
|
61
|
-
onClick={() => setOpen(true)}
|
61
|
+
onClick={() => { setOpen(true); }}
|
62
62
|
>
|
63
63
|
Send Pairing Emails
|
64
64
|
</Button>
|
@@ -44,10 +44,10 @@ const PairingsTab: React.FC<PairingsTabProps> = ({ openClassical, onUpdate }) =>
|
|
44
44
|
const view = searchParams.get('view') || '1';
|
45
45
|
|
46
46
|
const round =
|
47
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
47
|
+
openClassical.sections[`${region}_${ratingRange}`].rounds[parseInt(view) - 1];
|
48
48
|
|
49
49
|
const maxRound =
|
50
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
50
|
+
openClassical.sections[`${region}_${ratingRange}`].rounds.length ?? 1;
|
51
51
|
|
52
52
|
return (
|
53
53
|
<Stack spacing={3}>
|
@@ -56,7 +56,7 @@ const PairingsTab: React.FC<PairingsTabProps> = ({ openClassical, onUpdate }) =>
|
|
56
56
|
<EmailPairingsButton
|
57
57
|
maxRound={maxRound}
|
58
58
|
currentRound={parseInt(view)}
|
59
|
-
emailsSent={round
|
59
|
+
emailsSent={round.pairingEmailsSent}
|
60
60
|
onSuccess={onUpdate}
|
61
61
|
/>
|
62
62
|
</Stack>
|
@@ -66,7 +66,7 @@ const PairingsTab: React.FC<PairingsTabProps> = ({ openClassical, onUpdate }) =>
|
|
66
66
|
label='Region'
|
67
67
|
select
|
68
68
|
value={region}
|
69
|
-
onChange={(e) => updateSearchParams('region', e.target.value)}
|
69
|
+
onChange={(e) => { updateSearchParams('region', e.target.value); }}
|
70
70
|
sx={{
|
71
71
|
flexGrow: 1,
|
72
72
|
}}
|
@@ -80,7 +80,7 @@ const PairingsTab: React.FC<PairingsTabProps> = ({ openClassical, onUpdate }) =>
|
|
80
80
|
label='Section'
|
81
81
|
select
|
82
82
|
value={ratingRange}
|
83
|
-
onChange={(e) => updateSearchParams('ratingRange', e.target.value)}
|
83
|
+
onChange={(e) => { updateSearchParams('ratingRange', e.target.value); }}
|
84
84
|
sx={{
|
85
85
|
flexGrow: 1,
|
86
86
|
}}
|
@@ -93,7 +93,7 @@ const PairingsTab: React.FC<PairingsTabProps> = ({ openClassical, onUpdate }) =>
|
|
93
93
|
label='Round'
|
94
94
|
select
|
95
95
|
value={view}
|
96
|
-
onChange={(e) => updateSearchParams('view', e.target.value)}
|
96
|
+
onChange={(e) => { updateSearchParams('view', e.target.value); }}
|
97
97
|
sx={{
|
98
98
|
flexGrow: 1,
|
99
99
|
}}
|
@@ -173,7 +173,7 @@ const AdminPairingsTable: React.FC<AdminPairingsTableProps> = ({
|
|
173
173
|
}, [setUpdatePairing]);
|
174
174
|
|
175
175
|
const pairings =
|
176
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
176
|
+
openClassical.sections[`${region}_${ratingRange}`].rounds[round - 1]?.pairings ??
|
177
177
|
[];
|
178
178
|
|
179
179
|
const onConfirmUpdate = () => {
|
@@ -228,7 +228,7 @@ const AdminPairingsTable: React.FC<AdminPairingsTableProps> = ({
|
|
228
228
|
onClose={
|
229
229
|
updateRequest.isLoading()
|
230
230
|
? undefined
|
231
|
-
: () => setUpdatePairing(undefined)
|
231
|
+
: () => { setUpdatePairing(undefined); }
|
232
232
|
}
|
233
233
|
maxWidth='sm'
|
234
234
|
fullWidth
|
@@ -249,7 +249,7 @@ const AdminPairingsTable: React.FC<AdminPairingsTableProps> = ({
|
|
249
249
|
select
|
250
250
|
required
|
251
251
|
value={updateResult}
|
252
|
-
onChange={(e) => setUpdateResult(e.target.value)}
|
252
|
+
onChange={(e) => { setUpdateResult(e.target.value); }}
|
253
253
|
sx={{ mt: 3, mb: 1, width: 1 }}
|
254
254
|
>
|
255
255
|
<MenuItem value='1-0'>White Wins (1-0)</MenuItem>
|
@@ -263,7 +263,7 @@ const AdminPairingsTable: React.FC<AdminPairingsTableProps> = ({
|
|
263
263
|
</DialogContent>
|
264
264
|
<DialogActions>
|
265
265
|
<Button
|
266
|
-
onClick={() => setUpdatePairing(undefined)}
|
266
|
+
onClick={() => { setUpdatePairing(undefined); }}
|
267
267
|
disabled={updateRequest.isLoading()}
|
268
268
|
>
|
269
269
|
Cancel
|
@@ -140,7 +140,7 @@ const PlayersTab: React.FC<PlayersTabProps> = ({ openClassical, onUpdate }) => {
|
|
140
140
|
const players = useMemo(
|
141
141
|
() =>
|
142
142
|
Object.values(
|
143
|
-
openClassical.sections[`${region}_${ratingRange}`]
|
143
|
+
openClassical.sections[`${region}_${ratingRange}`].players || {},
|
144
144
|
).filter((player) => player.lichessUsername !== 'No Opponent'),
|
145
145
|
[openClassical, region, ratingRange],
|
146
146
|
);
|
@@ -204,7 +204,7 @@ const PlayersTab: React.FC<PlayersTabProps> = ({ openClassical, onUpdate }) => {
|
|
204
204
|
label='Region'
|
205
205
|
select
|
206
206
|
value={region}
|
207
|
-
onChange={(e) => updateSearchParams('region', e.target.value)}
|
207
|
+
onChange={(e) => { updateSearchParams('region', e.target.value); }}
|
208
208
|
sx={{
|
209
209
|
flexGrow: 1,
|
210
210
|
}}
|
@@ -218,7 +218,7 @@ const PlayersTab: React.FC<PlayersTabProps> = ({ openClassical, onUpdate }) => {
|
|
218
218
|
label='Section'
|
219
219
|
select
|
220
220
|
value={ratingRange}
|
221
|
-
onChange={(e) => updateSearchParams('ratingRange', e.target.value)}
|
221
|
+
onChange={(e) => { updateSearchParams('ratingRange', e.target.value); }}
|
222
222
|
sx={{
|
223
223
|
flexGrow: 1,
|
224
224
|
}}
|
@@ -252,7 +252,7 @@ const PlayersTab: React.FC<PlayersTabProps> = ({ openClassical, onUpdate }) => {
|
|
252
252
|
<Dialog
|
253
253
|
open={Boolean(updatePlayer)}
|
254
254
|
onClose={
|
255
|
-
updateRequest.isLoading() ? undefined : () => setUpdatePlayer('')
|
255
|
+
updateRequest.isLoading() ? undefined : () => { setUpdatePlayer(''); }
|
256
256
|
}
|
257
257
|
maxWidth='sm'
|
258
258
|
fullWidth
|
@@ -269,7 +269,7 @@ const PlayersTab: React.FC<PlayersTabProps> = ({ openClassical, onUpdate }) => {
|
|
269
269
|
</DialogContent>
|
270
270
|
<DialogActions>
|
271
271
|
<Button
|
272
|
-
onClick={() => setUpdatePlayer('')}
|
272
|
+
onClick={() => { setUpdatePlayer(''); }}
|
273
273
|
disabled={updateRequest.isLoading()}
|
274
274
|
>
|
275
275
|
Cancel
|
@@ -21,7 +21,7 @@ const Tutorial: React.FC<TutorialProps> = ({ name, steps, zIndex }) => {
|
|
21
21
|
|
22
22
|
useEffect(() => {
|
23
23
|
if (
|
24
|
-
(!user.tutorials
|
24
|
+
(!user.tutorials?.[name]) &&
|
25
25
|
tutorialState.activeTutorial !== name
|
26
26
|
) {
|
27
27
|
console.log('Starting tutorial: ', name);
|
@@ -41,7 +41,7 @@ const Tutorial: React.FC<TutorialProps> = ({ name, steps, zIndex }) => {
|
|
41
41
|
.then(() => {
|
42
42
|
setTutorialState({});
|
43
43
|
})
|
44
|
-
.catch((err) => console.error('completeTutorial: ', err));
|
44
|
+
.catch((err) => { console.error('completeTutorial: ', err); });
|
45
45
|
}
|
46
46
|
},
|
47
47
|
[setTutorialState, api, user.tutorials, name]
|
@@ -116,7 +116,7 @@ const PriceMatrix: React.FC<PriceMatrixProps> = ({
|
|
116
116
|
fullWidth
|
117
117
|
loading={request?.isLoading() && interval === 'month'}
|
118
118
|
disabled={request?.isLoading() && interval !== 'month'}
|
119
|
-
onClick={() => onSubscribe('month')}
|
119
|
+
onClick={() => { onSubscribe('month'); }}
|
120
120
|
color='subscribe'
|
121
121
|
>
|
122
122
|
Subscribe
|
@@ -159,7 +159,7 @@ const PriceMatrix: React.FC<PriceMatrixProps> = ({
|
|
159
159
|
fullWidth
|
160
160
|
loading={request?.isLoading() && interval === 'year'}
|
161
161
|
disabled={request?.isLoading() && interval !== 'year'}
|
162
|
-
onClick={() => onSubscribe('year')}
|
162
|
+
onClick={() => { onSubscribe('year'); }}
|
163
163
|
color='subscribe'
|
164
164
|
>
|
165
165
|
Subscribe
|
@@ -78,7 +78,7 @@ const UpsellDialog: React.FC<UpsellDialogProps> = ({
|
|
78
78
|
maxWidth='sm'
|
79
79
|
fullWidth
|
80
80
|
open={open}
|
81
|
-
onClose={() => onClose(false)}
|
81
|
+
onClose={() => { onClose(false); }}
|
82
82
|
>
|
83
83
|
<DialogTitle>Upgrade to a Full Account</DialogTitle>
|
84
84
|
<DialogContent>
|
@@ -100,7 +100,7 @@ const UpsellDialog: React.FC<UpsellDialogProps> = ({
|
|
100
100
|
</DialogContentText>
|
101
101
|
</DialogContent>
|
102
102
|
<DialogActions>
|
103
|
-
<Button onClick={() => onClose(false)}>Cancel</Button>
|
103
|
+
<Button onClick={() => { onClose(false); }}>Cancel</Button>
|
104
104
|
<Button onClick={onViewPrices} href='/prices'>
|
105
105
|
View Prices
|
106
106
|
</Button>
|
@@ -12,7 +12,7 @@ const UpsellPage: React.FC<UpsellPageProps> = ({ redirectTo, ...props }) => {
|
|
12
12
|
|
13
13
|
return (
|
14
14
|
<Container maxWidth='lg' sx={{ pt: 5 }}>
|
15
|
-
<UpsellDialog open={true} onClose={() => navigate(redirectTo)} {...props} />
|
15
|
+
<UpsellDialog open={true} onClose={() => { navigate(redirectTo); }} {...props} />
|
16
16
|
</Container>
|
17
17
|
);
|
18
18
|
};
|