From 9fc95a9ce39fa6608c7dae26129a7bc3e5661ced Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 13:33:24 +0000
Subject: [PATCH 01/61] client/modules/User/reducers: update to ts, no-verify
---
client/modules/User/{reducers.js => reducers.ts} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/{reducers.js => reducers.ts} (100%)
diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.ts
similarity index 100%
rename from client/modules/User/reducers.js
rename to client/modules/User/reducers.ts
From fda66326749c8ffb88cf1363148cad019bdea77c Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 14:29:43 +0000
Subject: [PATCH 02/61] client/modules/Users/reducers: update to named export
and add types, wip no-verify
---
client/modules/User/reducers.ts | 12 +++++++++---
client/reducers.ts | 2 +-
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/client/modules/User/reducers.ts b/client/modules/User/reducers.ts
index 2832190b28..f043eb8f0f 100644
--- a/client/modules/User/reducers.ts
+++ b/client/modules/User/reducers.ts
@@ -1,6 +1,14 @@
+import type { CookieConsentOptions, PublicUser } from '../../../common/types';
import * as ActionTypes from '../../constants';
-const user = (state = { authenticated: false }, action) => {
+// User Action:
+export type UserAction = {
+ user?: PublicUser;
+ cookieConsent?: CookieConsentOptions;
+ type: any;
+};
+
+export const user = (state = { authenticated: false }, action: UserAction) => {
switch (action.type) {
case ActionTypes.AUTH_USER:
return {
@@ -47,5 +55,3 @@ const user = (state = { authenticated: false }, action) => {
return state;
}
};
-
-export default user;
diff --git a/client/reducers.ts b/client/reducers.ts
index 2c65e555ca..47d9627f80 100644
--- a/client/reducers.ts
+++ b/client/reducers.ts
@@ -4,7 +4,7 @@ import ide from './modules/IDE/reducers/ide';
import { preferences } from './modules/IDE/reducers/preferences';
import project from './modules/IDE/reducers/project';
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
-import user from './modules/User/reducers';
+import { user } from './modules/User/reducers';
import sketches from './modules/IDE/reducers/projects';
import toast from './modules/IDE/reducers/toast';
import console from './modules/IDE/reducers/console';
From 9623321e2a87efe471859a1ff753bd71d33fd3f3 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 14:30:30 +0000
Subject: [PATCH 03/61] server/types/apiKey: add token? to apiKey
---
server/types/apiKey.ts | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/server/types/apiKey.ts b/server/types/apiKey.ts
index 21ba15aa04..443a30e3e4 100644
--- a/server/types/apiKey.ts
+++ b/server/types/apiKey.ts
@@ -8,6 +8,7 @@ export interface IApiKey extends VirtualId, MongooseTimestamps {
label: string;
lastUsedAt?: Date;
hashedKey: string;
+ token?: string;
}
/** Mongoose document object for API Key */
@@ -23,7 +24,10 @@ export interface ApiKeyDocument
* and can be exposed to the client
*/
export interface SanitisedApiKey
- extends Pick {}
+ extends Pick<
+ ApiKeyDocument,
+ 'id' | 'label' | 'lastUsedAt' | 'createdAt' | 'token'
+ > {}
/** Mongoose model for API Key */
export interface ApiKeyModel extends Model {}
From 353342540fabd39311da7e9a4d1b35a2f2b23c27 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 14:44:58 +0000
Subject: [PATCH 04/61] server/PublicUser: update apiKeys to be sanitised api
keys
---
server/types/user.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/server/types/user.ts b/server/types/user.ts
index 6d6d53fa1f..509da313b7 100644
--- a/server/types/user.ts
+++ b/server/types/user.ts
@@ -2,7 +2,7 @@ import { Document, Model, Types } from 'mongoose';
import { VirtualId, MongooseTimestamps } from './mongoose';
import { UserPreferences, CookieConsentOptions } from './userPreferences';
import { EmailConfirmationStates } from './email';
-import { ApiKeyDocument } from './apiKey';
+import { ApiKeyDocument, SanitisedApiKey } from './apiKey';
import { Error, GenericResponseBody, RouteParam } from './express';
// -------- MONGOOSE --------
@@ -38,14 +38,16 @@ export interface PublicUser
| 'email'
| 'username'
| 'preferences'
- | 'apiKeys'
| 'verified'
| 'id'
| 'totalSize'
| 'github'
| 'google'
| 'cookieConsent'
- > {}
+ > {
+ /** Can contain either raw ApiKeyDocuments (server side) or SanitisedApiKeys (client side) */
+ apiKeys: SanitisedApiKey[];
+}
/** Mongoose document object for User */
export interface UserDocument
From 6bcdebff94d6eeac78167347b4f979ab56c54875 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 14:58:00 +0000
Subject: [PATCH 05/61] update UserState in root redux to include User
---
client/modules/User/reducers.ts | 9 ++++++++-
server/types/user.ts | 3 ++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/client/modules/User/reducers.ts b/client/modules/User/reducers.ts
index f043eb8f0f..d8a7141683 100644
--- a/client/modules/User/reducers.ts
+++ b/client/modules/User/reducers.ts
@@ -8,7 +8,14 @@ export type UserAction = {
type: any;
};
-export const user = (state = { authenticated: false }, action: UserAction) => {
+export const user = (
+ state: Partial & {
+ authenticated: boolean;
+ } = {
+ authenticated: false
+ },
+ action: UserAction
+) => {
switch (action.type) {
case ActionTypes.AUTH_USER:
return {
diff --git a/server/types/user.ts b/server/types/user.ts
index 509da313b7..5d2eb61f34 100644
--- a/server/types/user.ts
+++ b/server/types/user.ts
@@ -22,7 +22,7 @@ export interface IUser extends VirtualId, MongooseTimestamps {
tokens: { kind: string }[];
apiKeys: Types.DocumentArray;
preferences: UserPreferences;
- totalSize: number;
+ totalSize?: number;
cookieConsent: CookieConsentOptions;
banned: boolean;
lastLoginTimestamp?: Date;
@@ -44,6 +44,7 @@ export interface PublicUser
| 'github'
| 'google'
| 'cookieConsent'
+ | 'totalSize'
> {
/** Can contain either raw ApiKeyDocuments (server side) or SanitisedApiKeys (client side) */
apiKeys: SanitisedApiKey[];
From 2c0342d4d9cf77c352ee649180bcbb4e7634f018 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 14:58:56 +0000
Subject: [PATCH 06/61] resolve remaining type-errors from PublicUser vs User
apiKey differences
---
client/testData/testReduxStore.ts | 6 ++++-
.../user.controller/__testUtils__.ts | 5 ++--
.../user.controller/__tests__/apiKey.test.ts | 27 ++++++++++++-------
.../authManagement/passwordManagement.test.ts | 26 +++++++++++-------
.../authManagement/updateSettings.test.ts | 17 +++++++-----
5 files changed, 51 insertions(+), 30 deletions(-)
diff --git a/client/testData/testReduxStore.ts b/client/testData/testReduxStore.ts
index 33223ae950..daf663ef92 100644
--- a/client/testData/testReduxStore.ts
+++ b/client/testData/testReduxStore.ts
@@ -54,7 +54,11 @@ const initialTestState: RootState = {
user: {
email: 'happydog@example.com',
username: 'happydog',
- preferences: {},
+ preferences: {
+ ...initialPrefState,
+ indentationAmount: 2,
+ isTabIndent: true
+ },
apiKeys: [],
verified: 'sent',
id: '123456789',
diff --git a/server/controllers/user.controller/__testUtils__.ts b/server/controllers/user.controller/__testUtils__.ts
index 9d851c49f8..1a7bf31cd6 100644
--- a/server/controllers/user.controller/__testUtils__.ts
+++ b/server/controllers/user.controller/__testUtils__.ts
@@ -29,7 +29,7 @@ export const mockBaseUserSanitised: PublicUser = {
email: 'test@example.com',
username: 'tester',
preferences: mockUserPreferences,
- apiKeys: ([] as unknown) as Types.DocumentArray,
+ apiKeys: [],
verified: 'verified',
id: 'abc123',
totalSize: 42,
@@ -42,6 +42,7 @@ export const mockBaseUserSanitised: PublicUser = {
export const mockBaseUserFull: Omit = {
...mockBaseUserSanitised,
name: 'test user',
+ apiKeys: ([] as unknown) as Types.DocumentArray,
tokens: [],
password: 'abweorij',
resetPasswordToken: '1i14ij23',
@@ -58,7 +59,7 @@ export const mockBaseUserFull: Omit = {
export function createMockUser(
overrides: Partial = {},
unSanitised: boolean = false
-): PublicUser & Record {
+): (PublicUser | UserDocument) & Record {
return {
...(unSanitised ? mockBaseUserFull : mockBaseUserSanitised),
...overrides
diff --git a/server/controllers/user.controller/__tests__/apiKey.test.ts b/server/controllers/user.controller/__tests__/apiKey.test.ts
index 875b15b2b6..47a0b8fe8c 100644
--- a/server/controllers/user.controller/__tests__/apiKey.test.ts
+++ b/server/controllers/user.controller/__tests__/apiKey.test.ts
@@ -7,7 +7,11 @@ import { Types } from 'mongoose';
import { User } from '../../../models/user';
import { createApiKey, removeApiKey } from '../apiKey';
-import type { ApiKeyDocument, RemoveApiKeyRequestParams } from '../../../types';
+import type {
+ ApiKeyDocument,
+ RemoveApiKeyRequestParams,
+ UserDocument
+} from '../../../types';
import { createMockUser } from '../__testUtils__';
jest.mock('../../../models/user');
@@ -31,7 +35,7 @@ describe('user.controller > api key', () => {
describe('createApiKey', () => {
it("returns an error if user doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
User.findById = jest.fn().mockResolvedValue(null);
@@ -48,7 +52,7 @@ describe('user.controller > api key', () => {
});
it('returns an error if label not provided', async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
request.body = {};
const user = new User();
@@ -98,7 +102,7 @@ describe('user.controller > api key', () => {
describe('removeApiKey', () => {
it("returns an error if user doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
User.findById = jest.fn().mockResolvedValue(null);
@@ -115,7 +119,7 @@ describe('user.controller > api key', () => {
});
it("returns an error if specified key doesn't exist", async () => {
- request.user = createMockUser({ id: '1234' });
+ request.user = createMockUser({ id: '1234' }, true);
request.params = { keyId: 'not-a-real-key' };
const user = new User();
user.apiKeys = ([] as unknown) as Types.DocumentArray;
@@ -145,11 +149,14 @@ describe('user.controller > api key', () => {
apiKeys.find = Array.prototype.find;
apiKeys.pull = jest.fn();
- const user = createMockUser({
- id: '1234',
- apiKeys,
- save: jest.fn()
- });
+ const user = createMockUser(
+ {
+ id: '1234',
+ apiKeys,
+ save: jest.fn()
+ },
+ true
+ ) as UserDocument;
request.user = user;
request.params = { keyId: 'id1' };
diff --git a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
index 248eeac4e0..b5336bffe4 100644
--- a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
@@ -29,7 +29,7 @@ describe('user.controller > auth management > password management', () => {
let response: MockResponse;
let next: MockNext;
let mockToken: string;
- let mockUser: Partial;
+ let mockUser: UserDocument;
const fixedTime = 100000000;
beforeEach(() => {
@@ -68,10 +68,13 @@ describe('user.controller > auth management > password management', () => {
describe('if the user is found', () => {
beforeEach(async () => {
mockToken = 'mock-token';
- mockUser = createMockUser({
- email: 'test@example.com',
- save: jest.fn().mockResolvedValue(null)
- });
+ mockUser = createMockUser(
+ {
+ email: 'test@example.com',
+ save: jest.fn().mockResolvedValue(null)
+ },
+ false
+ ) as UserDocument;
(generateToken as jest.Mock).mockResolvedValue(mockToken);
User.findByEmail = jest.fn().mockResolvedValue(mockUser);
@@ -143,10 +146,13 @@ describe('user.controller > auth management > password management', () => {
});
it('returns unsuccessful for all other errors', async () => {
mockToken = 'mock-token';
- mockUser = createMockUser({
- email: 'test@example.com',
- save: jest.fn().mockResolvedValue(null)
- });
+ mockUser = createMockUser(
+ {
+ email: 'test@example.com',
+ save: jest.fn().mockResolvedValue(null)
+ },
+ false
+ ) as UserDocument;
(generateToken as jest.Mock).mockRejectedValue(
new Error('network error')
@@ -298,7 +304,7 @@ describe('user.controller > auth management > password management', () => {
resetPasswordToken: 'valid-token',
resetPasswordExpires: fixedTime + 10000, // still valid
save: jest.fn()
- };
+ } as UserDocument;
beforeEach(async () => {
User.findOne = jest.fn().mockReturnValue({
diff --git a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
index 698dfa1aea..faf6a89297 100644
--- a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
@@ -60,13 +60,16 @@ describe('user.controller > auth management > updateSettings (email, username, p
response = new MockResponse();
next = jest.fn();
- startingUser = createMockUser({
- username: OLD_USERNAME,
- email: OLD_EMAIL,
- password: OLD_PASSWORD,
- id: '123459',
- comparePassword: jest.fn().mockResolvedValue(true)
- });
+ startingUser = createMockUser(
+ {
+ username: OLD_USERNAME,
+ email: OLD_EMAIL,
+ password: OLD_PASSWORD,
+ id: '123459',
+ comparePassword: jest.fn().mockResolvedValue(true)
+ },
+ false
+ ) as UserDocument;
testUser = { ...startingUser }; // copy to avoid mutation causing false-positive tests results
From 70880de60bcdd0c5652e4bf2a4b48186e2c08e8f Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 15:16:28 +0000
Subject: [PATCH 07/61] client/modules/User/actions: update to ts, no-verify
---
client/modules/User/{actions.js => actions.ts} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/{actions.js => actions.ts} (100%)
diff --git a/client/modules/User/actions.js b/client/modules/User/actions.ts
similarity index 100%
rename from client/modules/User/actions.js
rename to client/modules/User/actions.ts
From 0ddb69db766fda3a8ae4595e24e404eda994e356 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 15:26:43 +0000
Subject: [PATCH 08/61] client/modules/User/actions: add types from server user
request/response types with flexibility
---
client/modules/User/actions.ts | 247 ++++++++++++++++++++++++---------
1 file changed, 185 insertions(+), 62 deletions(-)
diff --git a/client/modules/User/actions.ts b/client/modules/User/actions.ts
index 20e6729b0d..ad47bc4d29 100644
--- a/client/modules/User/actions.ts
+++ b/client/modules/User/actions.ts
@@ -1,79 +1,124 @@
import { FORM_ERROR } from 'final-form';
+import type { AnyAction, Dispatch } from 'redux';
+import type { ThunkDispatch } from 'redux-thunk';
import * as ActionTypes from '../../constants';
import browserHistory from '../../browserHistory';
import { apiClient } from '../../utils/apiClient';
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
import { setLanguage } from '../IDE/actions/preferences';
import { showToast, setToastText } from '../IDE/actions/toast';
+import type {
+ CreateApiKeyRequestBody,
+ CreateUserRequestBody,
+ Error,
+ PublicUser,
+ PublicUserOrError,
+ PublicUserOrErrorOrGeneric,
+ RemoveApiKeyRequestParams,
+ ResetOrUpdatePasswordRequestParams,
+ ResetPasswordInitiateRequestBody,
+ UpdateCookieConsentRequestBody,
+ UpdatePasswordRequestBody,
+ UpdateSettingsRequestBody,
+ UserPreferences,
+ VerifyEmailQuery
+} from '../../../common/types';
+import { RootState } from '../../reducers';
-export function authError(error) {
+export function authError(error: Error) {
return {
type: ActionTypes.AUTH_ERROR,
payload: error
};
}
-export function signUpUser(formValues) {
+/**
+ * - Method: `POST`
+ * - Endpoint: `/signup`
+ * - Authenticated: `false`
+ * - Id: `UserController.createUser`
+ *
+ * Description:
+ * - Create a new user
+ */
+export function signUpUser(formValues: CreateUserRequestBody) {
return apiClient.post('/signup', formValues);
}
-export function loginUser(formValues) {
+export function loginUser(formValues: { email: string; password: string }) {
return apiClient.post('/login', formValues);
}
-export function authenticateUser(user) {
+export function authenticateUser(user: PublicUser) {
return {
type: ActionTypes.AUTH_USER,
user
};
}
-export function loginUserFailure(error) {
+export function loginUserFailure(error: Error) {
return {
type: ActionTypes.AUTH_ERROR,
error
};
}
-export function setPreferences(preferences) {
+export function setPreferences(preferences: UserPreferences) {
return {
type: ActionTypes.SET_PREFERENCES,
preferences
};
}
-export function validateAndLoginUser(formProps) {
- return (dispatch, getState) => {
+export function validateAndLoginUser(formProps: {
+ email: string;
+ password: string;
+}) {
+ return (
+ dispatch: ThunkDispatch,
+ getState: () => RootState
+ ) => {
const state = getState();
const { previousPath } = state.ide;
- return new Promise((resolve) => {
- loginUser(formProps)
- .then((response) => {
- dispatch(authenticateUser(response.data));
- dispatch(setPreferences(response.data.preferences));
- dispatch(
- setLanguage(response.data.preferences.language, {
- persistPreference: false
+ return new Promise(
+ (resolve) => {
+ loginUser(formProps)
+ .then((response) => {
+ dispatch(authenticateUser(response.data));
+ dispatch(setPreferences(response.data.preferences));
+ dispatch(
+ setLanguage(response.data.preferences.language, {
+ persistPreference: false
+ })
+ );
+ dispatch(justOpenedProject());
+ browserHistory.push(previousPath);
+ resolve();
+ })
+ .catch((error) =>
+ resolve({
+ [FORM_ERROR]: error.response.data.message
})
);
- dispatch(justOpenedProject());
- browserHistory.push(previousPath);
- resolve();
- })
- .catch((error) =>
- resolve({
- [FORM_ERROR]: error.response.data.message
- })
- );
- });
+ }
+ );
};
}
-export function validateAndSignUpUser(formValues) {
- return (dispatch, getState) => {
+/**
+ * - Method: `POST`
+ * - Endpoint: `/signup`
+ * - Authenticated: `false`
+ * - Id: `UserController.createUser`
+ *
+ * Description:
+ * - Create a new user
+ */
+export function validateAndSignUpUser(formValues: CreateUserRequestBody) {
+ return (dispatch: Dispatch, getState: () => RootState) => {
const state = getState();
const { previousPath } = state.ide;
- return new Promise((resolve) => {
+ return new Promise((resolve) => {
signUpUser(formValues)
.then((response) => {
dispatch(authenticateUser(response.data));
@@ -91,7 +136,7 @@ export function validateAndSignUpUser(formValues) {
}
export function getUser() {
- return async (dispatch) => {
+ return async (dispatch: Dispatch) => {
try {
const response = await apiClient.get('/session');
const { data } = response;
@@ -106,7 +151,7 @@ export function getUser() {
preferences: data.preferences
});
setLanguage(data.preferences.language, { persistPreference: false });
- } catch (error) {
+ } catch (error: any) {
const message = error.response
? error.response.data.error || error.response.message
: 'Unknown error.';
@@ -116,7 +161,7 @@ export function getUser() {
}
export function validateSession() {
- return async (dispatch, getState) => {
+ return async (dispatch: Dispatch, getState: () => RootState) => {
try {
const response = await apiClient.get('/session');
const state = getState();
@@ -124,7 +169,7 @@ export function validateSession() {
if (state.user.username !== response.data.username) {
dispatch(showErrorModal('staleSession'));
}
- } catch (error) {
+ } catch (error: any) {
if (error.response && error.response.status === 404) {
dispatch(showErrorModal('staleSession'));
}
@@ -132,7 +177,7 @@ export function validateSession() {
};
}
-export function resetProject(dispatch) {
+export function resetProject(dispatch: Dispatch) {
dispatch({
type: ActionTypes.RESET_PROJECT
});
@@ -143,7 +188,7 @@ export function resetProject(dispatch) {
}
export function logoutUser() {
- return (dispatch) => {
+ return (dispatch: Dispatch) => {
apiClient
.get('/logout')
.then(() => {
@@ -159,9 +204,20 @@ export function logoutUser() {
};
}
-export function initiateResetPassword(formValues) {
- return (dispatch) =>
- new Promise((resolve) => {
+/**
+ * - Method: `POST`
+ * - Endpoint: `/reset-password`
+ * - Authenticated: `false`
+ * - Id: `UserController.resetPasswordInitiate`
+ *
+ * Description:
+ * - Send an Reset-Password email to the registered email account
+ */
+export function initiateResetPassword(
+ formValues: ResetPasswordInitiateRequestBody
+) {
+ return (dispatch: Dispatch) =>
+ new Promise((resolve) => {
dispatch({
type: ActionTypes.RESET_PASSWORD_INITIATE
});
@@ -179,8 +235,17 @@ export function initiateResetPassword(formValues) {
});
}
+/**
+ * - Method: `POST`
+ * - Endpoint: `/verify/send`
+ * - Authenticated: `false`
+ * - Id: `UserController.emailVerificationInitiate`
+ *
+ * Description:
+ * - Send a Confirm Email email to verify that the user owns the specified email account
+ */
export function initiateVerification() {
- return (dispatch) => {
+ return (dispatch: Dispatch) => {
dispatch({
type: ActionTypes.EMAIL_VERIFICATION_INITIATE
});
@@ -199,8 +264,17 @@ export function initiateVerification() {
};
}
-export function verifyEmailConfirmation(token) {
- return (dispatch) => {
+/**
+ * - Method: `GET`
+ * - Endpoint: `/verify`
+ * - Authenticated: `false`
+ * - Id: `UserController.verifyEmail`
+ *
+ * Description:
+ * - Used in the Confirm Email's link to verify a user's email is attached to their account
+ */
+export function verifyEmailConfirmation(token: VerifyEmailQuery['t']) {
+ return (dispatch: Dispatch) => {
dispatch({
type: ActionTypes.EMAIL_VERIFICATION_VERIFY,
state: 'checking'
@@ -229,8 +303,21 @@ export function resetPasswordReset() {
};
}
-export function validateResetPasswordToken(token) {
- return (dispatch) => {
+/**
+ * - Method: `GET`
+ * - Endpoint: `/reset-password/:token`
+ * - Authenticated: `false`
+ * - Id: `UserController.validateResetPasswordToken`
+ *
+ * Description:
+ * - The link in the Reset Password email, which contains a reset token that is valid for 1h
+ * - If valid, the user will see a form to reset their password
+ * - Else they will see a message that their token has expired
+ */
+export function validateResetPasswordToken(
+ token: ResetOrUpdatePasswordRequestParams['token']
+) {
+ return (dispatch: Dispatch) => {
apiClient
.get(`/reset-password/${token}`)
.then(() => {
@@ -244,9 +331,24 @@ export function validateResetPasswordToken(token) {
};
}
-export function updatePassword(formValues, token) {
- return (dispatch) =>
- new Promise((resolve) =>
+/**
+ * - Method: `POST`
+ * - Endpoint: `/reset-password/:token`
+ * - Authenticated: `false`
+ * - Id: `UserController.updatePassword`
+ *
+ * Description:
+ * - Used by the new password form to update a user's password with the valid token
+ * - Returns a Generic 401 - 'Password reset token is invalid or has expired.' if the token timed out
+ * - Returns a PublicUser if successfully saved
+ * - Returns an Error if network error on save attempt
+ */
+export function updatePassword(
+ formValues: UpdatePasswordRequestBody,
+ token: ResetOrUpdatePasswordRequestParams['token']
+) {
+ return (dispatch: Dispatch) =>
+ new Promise((resolve) =>
apiClient
.post(`/reset-password/${token}`, formValues)
.then((response) => {
@@ -263,27 +365,46 @@ export function updatePassword(formValues, token) {
);
}
-export function updateSettingsSuccess(user) {
+export function updateSettingsSuccess(user: PublicUser) {
return {
type: ActionTypes.SETTINGS_UPDATED,
user
};
}
-export function submitSettings(formValues) {
+/**
+ * - Method: `PUT`
+ * - Endpoint: `/account`
+ * - Authenticated: `true`
+ * - Id: `UserController.updateSettings`
+ *
+ * Description:
+ * - Used to update the user's username, email, or password on the `/account` page while authenticated
+ * - Currently the client only shows the `currentPassword` and `newPassword` fields if no social logins (github & google) are enabled
+ */
+export function submitSettings(formValues: UpdateSettingsRequestBody) {
return apiClient.put('/account', formValues);
}
-
-export function updateSettings(formValues) {
- return (dispatch) =>
- new Promise((resolve) => {
+/**
+ * - Method: `PUT`
+ * - Endpoint: `/account`
+ * - Authenticated: `true`
+ * - Id: `UserController.updateSettings`
+ *
+ * Description:
+ * - Used to update the user's username, email, or password on the `/account` page while authenticated
+ * - Currently the client only shows the `currentPassword` and `newPassword` fields if no social logins (github & google) are enabled
+ */
+export function updateSettings(formValues: Partial) {
+ return (dispatch: ThunkDispatch) =>
+ new Promise((resolve) => {
if (!formValues.currentPassword && formValues.newPassword) {
dispatch(showToast(5500));
dispatch(setToastText('Toast.EmptyCurrentPass'));
resolve();
return;
}
- submitSettings(formValues)
+ submitSettings(formValues as UpdateSettingsRequestBody)
.then((response) => {
dispatch(updateSettingsSuccess(response.data));
dispatch(showToast(5500));
@@ -313,15 +434,15 @@ export function updateSettings(formValues) {
});
}
-export function createApiKeySuccess(user) {
+export function createApiKeySuccess(user: PublicUser) {
return {
type: ActionTypes.API_KEY_CREATED,
user
};
}
-export function createApiKey(label) {
- return (dispatch) =>
+export function createApiKey(label: CreateApiKeyRequestBody['label']) {
+ return (dispatch: Dispatch) =>
apiClient
.post('/account/api-keys', { label })
.then((response) => {
@@ -333,8 +454,8 @@ export function createApiKey(label) {
});
}
-export function removeApiKey(keyId) {
- return (dispatch) =>
+export function removeApiKey(keyId: RemoveApiKeyRequestParams['keyId']) {
+ return (dispatch: Dispatch) =>
apiClient
.delete(`/account/api-keys/${keyId}`)
.then((response) => {
@@ -349,8 +470,8 @@ export function removeApiKey(keyId) {
});
}
-export function unlinkService(service) {
- return (dispatch) => {
+export function unlinkService(service: string) {
+ return (dispatch: Dispatch) => {
if (!['github', 'google'].includes(service)) return;
apiClient
.delete(`/auth/${service}`)
@@ -365,9 +486,11 @@ export function unlinkService(service) {
};
}
-export function setUserCookieConsent(cookieConsent) {
+export function setUserCookieConsent(
+ cookieConsent: UpdateCookieConsentRequestBody['cookieConsent']
+) {
// maybe also send this to the server rn?
- return (dispatch) => {
+ return (dispatch: Dispatch) => {
apiClient
.put('/cookie-consent', { cookieConsent })
.then(() => {
From aae6687fe8f6ccdaf9fba48eb90125d8bc46a827 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:05:44 +0000
Subject: [PATCH 09/61] server/types/apiKey: update to add comment about token
property on last item of return of createApiKeys
---
server/controllers/user.controller/apiKey.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/server/controllers/user.controller/apiKey.ts b/server/controllers/user.controller/apiKey.ts
index e87747a75c..5a8cc613f0 100644
--- a/server/controllers/user.controller/apiKey.ts
+++ b/server/controllers/user.controller/apiKey.ts
@@ -67,7 +67,8 @@ export const createApiKey: RequestHandler<
await user.save();
const apiKeys = user.apiKeys.map((apiKey, index) => {
- const fields = apiKey.toObject!();
+ const fields = apiKey.toObject();
+ // only include the token of the most recently made apiKey to display in the copiable field
const shouldIncludeToken = index === addedApiKeyIndex - 1;
return shouldIncludeToken ? { ...fields, token: keyToBeHashed } : fields;
From 90335b3ac5221b07ccd3d0340dc2c2337ce21759 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:07:08 +0000
Subject: [PATCH 10/61] server/types/apiKey: update toJson method return type
to IApiKey
---
server/types/apiKey.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/types/apiKey.ts b/server/types/apiKey.ts
index 443a30e3e4..be21dfdbcd 100644
--- a/server/types/apiKey.ts
+++ b/server/types/apiKey.ts
@@ -15,8 +15,8 @@ export interface IApiKey extends VirtualId, MongooseTimestamps {
export interface ApiKeyDocument
extends IApiKey,
Omit, 'id'> {
- toJSON(options?: any): SanitisedApiKey;
- toObject(options?: any): SanitisedApiKey;
+ toJSON(options?: any): IApiKey;
+ toObject(options?: any): IApiKey;
}
/**
From 276356ff76af16420113989874634aeb7d7000a2 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:08:49 +0000
Subject: [PATCH 11/61] server/types/PublicUser: update with accurate
PublicUser apiKeys, should be sanitisedApiKeys. Add method for
sanitisingApiKeys
---
.../user.controller/__testUtils__.ts | 12 +++++++---
.../authManagement/3rdPartyManagement.test.ts | 23 ++++++++++++-------
.../user.controller/__tests__/helpers.test.ts | 19 ++++++++-------
server/controllers/user.controller/helpers.ts | 20 +++++++++++++---
4 files changed, 52 insertions(+), 22 deletions(-)
diff --git a/server/controllers/user.controller/__testUtils__.ts b/server/controllers/user.controller/__testUtils__.ts
index 1a7bf31cd6..7bc90bf0e6 100644
--- a/server/controllers/user.controller/__testUtils__.ts
+++ b/server/controllers/user.controller/__testUtils__.ts
@@ -59,9 +59,15 @@ export const mockBaseUserFull: Omit = {
export function createMockUser(
overrides: Partial = {},
unSanitised: boolean = false
-): (PublicUser | UserDocument) & Record {
+): PublicUser | UserDocument {
+ if (unSanitised) {
+ return {
+ ...mockBaseUserFull,
+ ...overrides
+ } as UserDocument;
+ }
return {
- ...(unSanitised ? mockBaseUserFull : mockBaseUserSanitised),
+ ...mockBaseUserSanitised,
...overrides
- };
+ } as PublicUser;
}
diff --git a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
index d2e68ee61a..2910b33569 100644
--- a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
@@ -5,6 +5,7 @@ import { Request, Response } from 'express';
import { unlinkGithub, unlinkGoogle } from '../../authManagement';
import { saveUser } from '../../helpers';
import { createMockUser } from '../../__testUtils__';
+import { UserDocument } from '../../../../types';
jest.mock('../../helpers', () => ({
...jest.requireActual('../../helpers'),
@@ -50,10 +51,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
});
});
describe('and when there is a user in the request', () => {
- const user = createMockUser({
- github: 'testuser',
- tokens: [{ kind: 'github' }, { kind: 'google' }]
- });
+ const user = createMockUser(
+ {
+ github: 'testuser',
+ tokens: [{ kind: 'github' }, { kind: 'google' }]
+ },
+ true
+ ) as UserDocument;
beforeEach(async () => {
request.user = user;
@@ -96,10 +100,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
});
});
describe('and when there is a user in the request', () => {
- const user = createMockUser({
- google: 'testuser',
- tokens: [{ kind: 'github' }, { kind: 'google' }]
- });
+ const user = createMockUser(
+ {
+ google: 'testuser',
+ tokens: [{ kind: 'github' }, { kind: 'google' }]
+ },
+ true
+ ) as UserDocument;
beforeEach(async () => {
request.user = user;
diff --git a/server/controllers/user.controller/__tests__/helpers.test.ts b/server/controllers/user.controller/__tests__/helpers.test.ts
index a6add4fda9..b04bb2548a 100644
--- a/server/controllers/user.controller/__tests__/helpers.test.ts
+++ b/server/controllers/user.controller/__tests__/helpers.test.ts
@@ -10,14 +10,17 @@ import { UserDocument } from '../../../types';
jest.mock('../../../models/user');
-const mockFullUser = createMockUser({
- // sensitive fields to be removed:
- name: 'bob dylan',
- tokens: [],
- password: 'password12314',
- resetPasswordToken: 'wijroaijwoer',
- banned: true
-});
+const mockFullUser = createMockUser(
+ {
+ // sensitive fields to be removed:
+ name: 'bob dylan',
+ tokens: [],
+ password: 'password12314',
+ resetPasswordToken: 'wijroaijwoer',
+ banned: true
+ },
+ true
+) as UserDocument;
const {
name,
diff --git a/server/controllers/user.controller/helpers.ts b/server/controllers/user.controller/helpers.ts
index 36bcf86649..7d05826fb1 100644
--- a/server/controllers/user.controller/helpers.ts
+++ b/server/controllers/user.controller/helpers.ts
@@ -1,19 +1,33 @@
import crypto from 'crypto';
import type { Response } from 'express';
import { User } from '../../models/user';
-import { PublicUser, UserDocument } from '../../types';
+import {
+ ApiKeyDocument,
+ PublicUser,
+ SanitisedApiKey,
+ UserDocument
+} from '../../types';
+
+export function sanitiseApiKey(key: ApiKeyDocument): SanitisedApiKey {
+ return {
+ id: key.id,
+ label: key.label,
+ lastUsedAt: key.lastUsedAt,
+ createdAt: key.createdAt
+ };
+}
/**
* Sanitise user objects to remove sensitive fields
* @param user
* @returns Sanitised user
*/
-export function userResponse(user: PublicUser | UserDocument): PublicUser {
+export function userResponse(user: UserDocument): PublicUser {
return {
email: user.email,
username: user.username,
preferences: user.preferences,
- apiKeys: user.apiKeys,
+ apiKeys: user.apiKeys.map((el) => sanitiseApiKey(el)),
verified: user.verified,
id: user.id,
totalSize: user.totalSize,
From ad4df7ab33cc03400dd455e31c7b6de48acec120 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:42:18 +0000
Subject: [PATCH 12/61] client/modules/User/reducers: update root state of
state.user to add password/email verification states + todo notes
---
client/modules/User/reducers.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/client/modules/User/reducers.ts b/client/modules/User/reducers.ts
index d8a7141683..08c07b94bf 100644
--- a/client/modules/User/reducers.ts
+++ b/client/modules/User/reducers.ts
@@ -11,6 +11,12 @@ export type UserAction = {
export const user = (
state: Partial & {
authenticated: boolean;
+ // TODO: use state of user from server as single source of truth:
+ // Currently using redux state below, but server also has similar info.
+ resetPasswordInitiate?: boolean;
+ resetPasswordInvalid?: boolean;
+ emailVerificationInitiate?: boolean;
+ emailVerificationTokenState?: boolean;
} = {
authenticated: false
},
From 997180a6d72d28b4b7c8aa7a6f3e9d84483a6078 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 03:36:22 +0000
Subject: [PATCH 13/61] server/types/user: remove typo for totalSize optional
---
server/types/user.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/types/user.ts b/server/types/user.ts
index 5d2eb61f34..1f180fee37 100644
--- a/server/types/user.ts
+++ b/server/types/user.ts
@@ -22,7 +22,7 @@ export interface IUser extends VirtualId, MongooseTimestamps {
tokens: { kind: string }[];
apiKeys: Types.DocumentArray;
preferences: UserPreferences;
- totalSize?: number;
+ totalSize: number;
cookieConsent: CookieConsentOptions;
banned: boolean;
lastLoginTimestamp?: Date;
From fa7d951d6ae0beeb1ad05673a186276d31288494 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:41:05 +0000
Subject: [PATCH 14/61] client/modules/User/pages/EmailVerificationView: update
to ts, no-verify
---
.../{EmailVerificationView.jsx => EmailVerificationView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{EmailVerificationView.jsx => EmailVerificationView.tsx} (100%)
diff --git a/client/modules/User/pages/EmailVerificationView.jsx b/client/modules/User/pages/EmailVerificationView.tsx
similarity index 100%
rename from client/modules/User/pages/EmailVerificationView.jsx
rename to client/modules/User/pages/EmailVerificationView.tsx
From c7f73e3d655d3674504aa2b574461002a32e76f5 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:43:45 +0000
Subject: [PATCH 15/61] client/modules/User/pages/EmailVerificationView: add
types for emailVerificationTokenState
---
client/modules/User/pages/EmailVerificationView.tsx | 6 +++---
client/modules/User/reducers.ts | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/modules/User/pages/EmailVerificationView.tsx b/client/modules/User/pages/EmailVerificationView.tsx
index d21d2446b9..7ce98d12fa 100644
--- a/client/modules/User/pages/EmailVerificationView.tsx
+++ b/client/modules/User/pages/EmailVerificationView.tsx
@@ -6,14 +6,15 @@ import { useTranslation } from 'react-i18next';
import { verifyEmailConfirmation } from '../actions';
import { RootPage } from '../../../components/RootPage';
import Nav from '../../IDE/components/Header/Nav';
+import type { RootState } from '../../../reducers';
-const EmailVerificationView = () => {
+export const EmailVerificationView = () => {
const { t } = useTranslation();
const location = useLocation();
const dispatch = useDispatch();
const browserHistory = useHistory();
const emailVerificationTokenState = useSelector(
- (state) => state.user.emailVerificationTokenState
+ (state: RootState) => state.user.emailVerificationTokenState
);
const verificationToken = useMemo(() => {
const searchParams = new URLSearchParams(location.search);
@@ -53,4 +54,3 @@ const EmailVerificationView = () => {
);
};
-export default EmailVerificationView;
diff --git a/client/modules/User/reducers.ts b/client/modules/User/reducers.ts
index 08c07b94bf..584ef24adb 100644
--- a/client/modules/User/reducers.ts
+++ b/client/modules/User/reducers.ts
@@ -16,7 +16,7 @@ export const user = (
resetPasswordInitiate?: boolean;
resetPasswordInvalid?: boolean;
emailVerificationInitiate?: boolean;
- emailVerificationTokenState?: boolean;
+ emailVerificationTokenState?: 'checking' | 'verified' | 'invalid';
} = {
authenticated: false
},
From 05998d921b275ab90a7d40dfb1f415b99490f26b Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:44:47 +0000
Subject: [PATCH 16/61] client/modules/User/pages/EmailVerificationView: update
to named export
---
client/routes.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/routes.jsx b/client/routes.jsx
index 8926a95bdd..42215ae611 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -13,7 +13,7 @@ import TermsOfUse from './modules/Legal/pages/TermsOfUse';
import LoginView from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
import ResetPasswordView from './modules/User/pages/ResetPasswordView';
-import EmailVerificationView from './modules/User/pages/EmailVerificationView';
+import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
import NewPasswordView from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView';
import CollectionView from './modules/User/pages/CollectionView';
From b56adb06b4b8633d96e3ac395b4c4e13ec4875e9 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:48:44 +0000
Subject: [PATCH 17/61] client/modules/User/pages/CollectionView: update to ts,
no-verify
---
.../modules/User/pages/{CollectionView.jsx => CollectionView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{CollectionView.jsx => CollectionView.tsx} (100%)
diff --git a/client/modules/User/pages/CollectionView.jsx b/client/modules/User/pages/CollectionView.tsx
similarity index 100%
rename from client/modules/User/pages/CollectionView.jsx
rename to client/modules/User/pages/CollectionView.tsx
From c57ae93dbe7f6ae3194e76e452d7bf42587061db Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:49:11 +0000
Subject: [PATCH 18/61] client/modules/User/pages/CollectionView: add tests and
update to named export
---
client/modules/User/pages/CollectionView.tsx | 7 +++----
client/routes.jsx | 2 +-
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/client/modules/User/pages/CollectionView.tsx b/client/modules/User/pages/CollectionView.tsx
index 8207388086..958b84737c 100644
--- a/client/modules/User/pages/CollectionView.tsx
+++ b/client/modules/User/pages/CollectionView.tsx
@@ -4,8 +4,9 @@ import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
import Collection from '../components/Collection';
-const CollectionView = () => {
- const params = useParams();
+export const CollectionView = () => {
+ // eslint-disable-next-line camelcase
+ const params = useParams<{ collection_id: string; username: string }>();
return (
@@ -17,5 +18,3 @@ const CollectionView = () => {
);
};
-
-export default CollectionView;
diff --git a/client/routes.jsx b/client/routes.jsx
index 42215ae611..d8b56f80f1 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -16,7 +16,7 @@ import ResetPasswordView from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
import NewPasswordView from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView';
-import CollectionView from './modules/User/pages/CollectionView';
+import { CollectionView } from './modules/User/pages/CollectionView';
import DashboardView from './modules/User/pages/DashboardView';
import { getUser } from './modules/User/actions';
import ProtectedSketchRoute from './protected-route';
From 2fe9aa92e7e06a2e9cf8cf64e147ada6ed160eb9 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:54:38 +0000
Subject: [PATCH 19/61] client/modules/User/pages/LoginView: update to ts,
no-verify
---
client/modules/User/pages/{LoginView.jsx => LoginView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{LoginView.jsx => LoginView.tsx} (100%)
diff --git a/client/modules/User/pages/LoginView.jsx b/client/modules/User/pages/LoginView.tsx
similarity index 100%
rename from client/modules/User/pages/LoginView.jsx
rename to client/modules/User/pages/LoginView.tsx
From 5db6d3a90698bd17f7c3e5a14af6cb188654177d Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:56:12 +0000
Subject: [PATCH 20/61] resolve merge conflict with LoginView
---
client/modules/User/pages/LoginView.tsx | 4 +---
client/routes.jsx | 10 +++++-----
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/client/modules/User/pages/LoginView.tsx b/client/modules/User/pages/LoginView.tsx
index b931c50ea8..70acbf8b31 100644
--- a/client/modules/User/pages/LoginView.tsx
+++ b/client/modules/User/pages/LoginView.tsx
@@ -7,7 +7,7 @@ import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
-function LoginView() {
+export function LoginView() {
const { t } = useTranslation();
return (
@@ -42,5 +42,3 @@ function LoginView() {
);
}
-
-export default LoginView;
diff --git a/client/routes.jsx b/client/routes.jsx
index d8b56f80f1..59f5fda169 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -6,11 +6,11 @@ import { Route as RouterRoute, Switch } from 'react-router-dom';
import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView';
-import About from './modules/About/pages/About';
-import CodeOfConduct from './modules/Legal/pages/CodeOfConduct';
-import PrivacyPolicy from './modules/Legal/pages/PrivacyPolicy';
-import TermsOfUse from './modules/Legal/pages/TermsOfUse';
-import LoginView from './modules/User/pages/LoginView';
+import { About } from './modules/About/pages/About';
+import { CodeOfConduct } from './modules/Legal/pages/CodeOfConduct';
+import { PrivacyPolicy } from './modules/Legal/pages/PrivacyPolicy';
+import { TermsOfUse } from './modules/Legal/pages/TermsOfUse';
+import { LoginView } from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
import ResetPasswordView from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
From a2a5a00492e1560b533a4bfdbdce56c116f7a2c7 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:57:46 +0000
Subject: [PATCH 21/61] client/modules/User/components/SocialAuthButton: update
to ts, no-verify
---
.../components/{SocialAuthButton.jsx => SocialAuthButton.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{SocialAuthButton.jsx => SocialAuthButton.tsx} (100%)
diff --git a/client/modules/User/components/SocialAuthButton.jsx b/client/modules/User/components/SocialAuthButton.tsx
similarity index 100%
rename from client/modules/User/components/SocialAuthButton.jsx
rename to client/modules/User/components/SocialAuthButton.tsx
From 10490e2b08b6ebdd07faf3ec07243b6b2dc8733b Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 22:59:04 +0000
Subject: [PATCH 22/61] client/modules/User/components/SocialAuthButton: update
to named export, no-verify
---
client/modules/User/components/SocialAuthButton.stories.jsx | 2 +-
client/modules/User/components/SocialAuthButton.tsx | 4 +---
client/modules/User/pages/AccountView.jsx | 2 +-
client/modules/User/pages/LoginView.tsx | 2 +-
client/modules/User/pages/SignupView.jsx | 2 +-
5 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/client/modules/User/components/SocialAuthButton.stories.jsx b/client/modules/User/components/SocialAuthButton.stories.jsx
index 770dd70162..a629dccc59 100644
--- a/client/modules/User/components/SocialAuthButton.stories.jsx
+++ b/client/modules/User/components/SocialAuthButton.stories.jsx
@@ -1,6 +1,6 @@
import React from 'react';
-import SocialAuthButton from './SocialAuthButton';
+import { SocialAuthButton } from './SocialAuthButton';
export default {
title: 'User/components/SocialAuthButton',
diff --git a/client/modules/User/components/SocialAuthButton.tsx b/client/modules/User/components/SocialAuthButton.tsx
index e48c660b67..6319413d43 100644
--- a/client/modules/User/components/SocialAuthButton.tsx
+++ b/client/modules/User/components/SocialAuthButton.tsx
@@ -34,7 +34,7 @@ const StyledButton = styled(Button)`
width: ${remSize(300)};
`;
-function SocialAuthButton({ service, linkStyle, isConnected }) {
+export function SocialAuthButton({ service, linkStyle, isConnected }) {
const { t } = useTranslation();
const ServiceIcon = icons[service];
@@ -94,5 +94,3 @@ SocialAuthButton.defaultProps = {
linkStyle: false,
isConnected: false
};
-
-export default SocialAuthButton;
diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx
index e9b3da7c9a..5bbb1ecc4a 100644
--- a/client/modules/User/pages/AccountView.jsx
+++ b/client/modules/User/pages/AccountView.jsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useHistory, useLocation } from 'react-router-dom';
import { parse } from 'query-string';
import AccountForm from '../components/AccountForm';
-import SocialAuthButton from '../components/SocialAuthButton';
+import { SocialAuthButton } from '../components/SocialAuthButton';
import APIKeyForm from '../components/APIKeyForm';
import Nav from '../../IDE/components/Header/Nav';
import ErrorModal from '../../IDE/components/ErrorModal';
diff --git a/client/modules/User/pages/LoginView.tsx b/client/modules/User/pages/LoginView.tsx
index 70acbf8b31..8b4776470f 100644
--- a/client/modules/User/pages/LoginView.tsx
+++ b/client/modules/User/pages/LoginView.tsx
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import LoginForm from '../components/LoginForm';
-import SocialAuthButton from '../components/SocialAuthButton';
+import { SocialAuthButton } from '../components/SocialAuthButton';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.jsx
index 16e038c6e0..ecd11503ef 100644
--- a/client/modules/User/pages/SignupView.jsx
+++ b/client/modules/User/pages/SignupView.jsx
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation, Trans } from 'react-i18next';
import SignupForm from '../components/SignupForm';
-import SocialAuthButton from '../components/SocialAuthButton';
+import { SocialAuthButton } from '../components/SocialAuthButton';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
From 01d99c7c7cb4b7ee3cc896c26ce0586f38c114e8 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:06:09 +0000
Subject: [PATCH 23/61] client/modules/User/components/SocialAuthButton: add
types & add SocialAuthServices enum
---
.../components/SocialAuthButton.stories.jsx | 6 ++--
.../User/components/SocialAuthButton.tsx | 32 ++++++++-----------
client/modules/User/pages/AccountView.jsx | 9 ++++--
client/modules/User/pages/LoginView.tsx | 9 ++++--
client/modules/User/pages/SignupView.jsx | 9 ++++--
5 files changed, 35 insertions(+), 30 deletions(-)
diff --git a/client/modules/User/components/SocialAuthButton.stories.jsx b/client/modules/User/components/SocialAuthButton.stories.jsx
index a629dccc59..5bd25dc16a 100644
--- a/client/modules/User/components/SocialAuthButton.stories.jsx
+++ b/client/modules/User/components/SocialAuthButton.stories.jsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { SocialAuthButton } from './SocialAuthButton';
+import { SocialAuthButton, SocialAuthServices } from './SocialAuthButton';
export default {
title: 'User/components/SocialAuthButton',
@@ -8,13 +8,13 @@ export default {
};
export const Github = () => (
-
+
Log in with Github
);
export const Google = () => (
-
+
Sign up with Google
);
diff --git a/client/modules/User/components/SocialAuthButton.tsx b/client/modules/User/components/SocialAuthButton.tsx
index 6319413d43..56ddc9ae5d 100644
--- a/client/modules/User/components/SocialAuthButton.tsx
+++ b/client/modules/User/components/SocialAuthButton.tsx
@@ -20,10 +20,10 @@ const icons = {
google: GoogleIcon
};
-const services = {
- github: 'github',
- google: 'google'
-};
+export enum SocialAuthServices {
+ github = 'github',
+ google = 'google'
+}
const servicesLabels = {
github: 'GitHub',
@@ -34,7 +34,16 @@ const StyledButton = styled(Button)`
width: ${remSize(300)};
`;
-export function SocialAuthButton({ service, linkStyle, isConnected }) {
+export interface SocialAuthButtonProps {
+ service: SocialAuthServices;
+ linkStyle?: boolean;
+ isConnected?: boolean;
+}
+export function SocialAuthButton({
+ service,
+ linkStyle = false,
+ isConnected = false
+}: SocialAuthButtonProps) {
const { t } = useTranslation();
const ServiceIcon = icons[service];
@@ -81,16 +90,3 @@ export function SocialAuthButton({ service, linkStyle, isConnected }) {
);
}
-
-SocialAuthButton.services = services;
-
-SocialAuthButton.propTypes = {
- service: PropTypes.oneOf(['github', 'google']).isRequired,
- linkStyle: PropTypes.bool,
- isConnected: PropTypes.bool
-};
-
-SocialAuthButton.defaultProps = {
- linkStyle: false,
- isConnected: false
-};
diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx
index 5bbb1ecc4a..508b65816d 100644
--- a/client/modules/User/pages/AccountView.jsx
+++ b/client/modules/User/pages/AccountView.jsx
@@ -6,7 +6,10 @@ import { useTranslation } from 'react-i18next';
import { useHistory, useLocation } from 'react-router-dom';
import { parse } from 'query-string';
import AccountForm from '../components/AccountForm';
-import { SocialAuthButton } from '../components/SocialAuthButton';
+import {
+ SocialAuthButton,
+ SocialAuthServices
+} from '../components/SocialAuthButton';
import APIKeyForm from '../components/APIKeyForm';
import Nav from '../../IDE/components/Header/Nav';
import ErrorModal from '../../IDE/components/ErrorModal';
@@ -28,12 +31,12 @@ function SocialLoginPanel() {
diff --git a/client/modules/User/pages/LoginView.tsx b/client/modules/User/pages/LoginView.tsx
index 8b4776470f..5df7b53951 100644
--- a/client/modules/User/pages/LoginView.tsx
+++ b/client/modules/User/pages/LoginView.tsx
@@ -3,7 +3,10 @@ import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import LoginForm from '../components/LoginForm';
-import { SocialAuthButton } from '../components/SocialAuthButton';
+import {
+ SocialAuthButton,
+ SocialAuthServices
+} from '../components/SocialAuthButton';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
@@ -21,8 +24,8 @@ export function LoginView() {
{t('LoginView.LoginOr')}
-
-
+
+
{t('LoginView.DontHaveAccount')}
diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.jsx
index ecd11503ef..3248a7bc61 100644
--- a/client/modules/User/pages/SignupView.jsx
+++ b/client/modules/User/pages/SignupView.jsx
@@ -3,7 +3,10 @@ import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation, Trans } from 'react-i18next';
import SignupForm from '../components/SignupForm';
-import { SocialAuthButton } from '../components/SocialAuthButton';
+import {
+ SocialAuthButton,
+ SocialAuthServices
+} from '../components/SocialAuthButton';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
@@ -23,8 +26,8 @@ function SignupView() {
{t('SignupView.Or')}
-
-
+
+
Date: Sun, 26 Oct 2025 23:09:30 +0000
Subject: [PATCH 24/61] client/modules/User/components/LoginForm: update to ts,
no-verfy
---
client/modules/User/components/{LoginForm.jsx => LoginForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{LoginForm.jsx => LoginForm.tsx} (100%)
diff --git a/client/modules/User/components/LoginForm.jsx b/client/modules/User/components/LoginForm.tsx
similarity index 100%
rename from client/modules/User/components/LoginForm.jsx
rename to client/modules/User/components/LoginForm.tsx
From 62cee39f61cbb0d8f59d545393b0d24be8e098e6 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:10:00 +0000
Subject: [PATCH 25/61] client/modules/User/components/LoginForm: update to
named export, no-verfy
---
client/modules/User/components/LoginForm.tsx | 4 +---
client/modules/User/components/LoginForm.unit.test.jsx | 2 +-
client/modules/User/pages/LoginView.tsx | 2 +-
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/client/modules/User/components/LoginForm.tsx b/client/modules/User/components/LoginForm.tsx
index 5bac902ecf..998c1090b4 100644
--- a/client/modules/User/components/LoginForm.tsx
+++ b/client/modules/User/components/LoginForm.tsx
@@ -8,7 +8,7 @@ import { validateLogin } from '../../../utils/reduxFormUtils';
import { validateAndLoginUser } from '../actions';
import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations';
-function LoginForm() {
+export function LoginForm() {
const { t, i18n } = useTranslation();
const dispatch = useDispatch();
@@ -114,5 +114,3 @@ function LoginForm() {
);
}
-
-export default LoginForm;
diff --git a/client/modules/User/components/LoginForm.unit.test.jsx b/client/modules/User/components/LoginForm.unit.test.jsx
index 2f4262c16b..1b6b982733 100644
--- a/client/modules/User/components/LoginForm.unit.test.jsx
+++ b/client/modules/User/components/LoginForm.unit.test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
-import LoginForm from './LoginForm';
+import { LoginForm } from './LoginForm';
import * as actions from '../actions';
import { initialTestState } from '../../../testData/testReduxStore';
import { reduxRender, screen, fireEvent, act } from '../../../test-utils';
diff --git a/client/modules/User/pages/LoginView.tsx b/client/modules/User/pages/LoginView.tsx
index 5df7b53951..4dff34b650 100644
--- a/client/modules/User/pages/LoginView.tsx
+++ b/client/modules/User/pages/LoginView.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
-import LoginForm from '../components/LoginForm';
+import { LoginForm } from '../components/LoginForm';
import {
SocialAuthButton,
SocialAuthServices
From e8750ed4def9f9925a09ea2e471ba475fe745713 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:14:02 +0000
Subject: [PATCH 26/61] client/modules/User/components/LoginForm: update with
types & update useSyncFormTranslations to handle null formRef
---
client/common/useSyncFormTranslations.ts | 4 ++--
client/modules/User/components/LoginForm.tsx | 10 +++++++---
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/client/common/useSyncFormTranslations.ts b/client/common/useSyncFormTranslations.ts
index 4a90362750..aed766aa43 100644
--- a/client/common/useSyncFormTranslations.ts
+++ b/client/common/useSyncFormTranslations.ts
@@ -12,11 +12,11 @@ export interface FormLike {
* @param language
*/
export const useSyncFormTranslations = (
- formRef: MutableRefObject,
+ formRef: MutableRefObject,
language: string
) => {
useEffect(() => {
- const form = formRef.current;
+ const form = formRef?.current;
if (!form) return;
const { values } = form.getState();
diff --git a/client/modules/User/components/LoginForm.tsx b/client/modules/User/components/LoginForm.tsx
index 998c1090b4..17bdaf9393 100644
--- a/client/modules/User/components/LoginForm.tsx
+++ b/client/modules/User/components/LoginForm.tsx
@@ -6,17 +6,21 @@ import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai';
import { Button, ButtonTypes } from '../../../common/Button';
import { validateLogin } from '../../../utils/reduxFormUtils';
import { validateAndLoginUser } from '../actions';
-import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations';
+import {
+ FormLike,
+ useSyncFormTranslations
+} from '../../../common/useSyncFormTranslations';
+import type { LoginForm as LoginFormType } from '../../../utils/reduxFormUtils';
export function LoginForm() {
const { t, i18n } = useTranslation();
const dispatch = useDispatch();
- function onSubmit(formProps) {
+ function onSubmit(formProps: LoginFormType) {
return dispatch(validateAndLoginUser(formProps));
}
const [showPassword, setShowPassword] = useState(false);
- const formRef = useRef(null);
+ const formRef = useRef(null);
const handleVisibility = () => {
setShowPassword(!showPassword);
From 0fcc6616ce7e2377b404891214a7b022d4d46104 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:22:02 +0000
Subject: [PATCH 27/61] client/modules/User/components/Notification: delete
unused file
---
.../modules/User/components/Notification.jsx | 22 -------------------
1 file changed, 22 deletions(-)
delete mode 100644 client/modules/User/components/Notification.jsx
diff --git a/client/modules/User/components/Notification.jsx b/client/modules/User/components/Notification.jsx
deleted file mode 100644
index c7189c3af1..0000000000
--- a/client/modules/User/components/Notification.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect } from 'react';
-import Cookies from 'js-cookie';
-import { useDispatch } from 'react-redux';
-import { showToast, setToastText } from '../../IDE/actions/toast';
-
-function Notification() {
- const dispatch = useDispatch();
- useEffect(() => {
- const notification = Cookies.get('p5-notification');
- if (!notification) {
- // show the toast
- dispatch(showToast(30000));
- const text = `There is a scheduled outage on Sunday, April 9 3AM - 5AM UTC.
- The entire site will be down, so please plan accordingly.`;
- dispatch(setToastText(text));
- Cookies.set('p5-notification', true, { expires: 365 });
- }
- });
- return null;
-}
-
-export default Notification;
From af68d0b0802e29fcf4412669f61fe7639ddc9707 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:24:48 +0000
Subject: [PATCH 28/61] client/modules/User/pages/NewPasswordView: update to
ts, no-verify
---
.../User/pages/{NewPasswordView.jsx => NewPasswordView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{NewPasswordView.jsx => NewPasswordView.tsx} (100%)
diff --git a/client/modules/User/pages/NewPasswordView.jsx b/client/modules/User/pages/NewPasswordView.tsx
similarity index 100%
rename from client/modules/User/pages/NewPasswordView.jsx
rename to client/modules/User/pages/NewPasswordView.tsx
From 77586121d31807c10f9b155d12a927e08c8ba26c Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:25:08 +0000
Subject: [PATCH 29/61] client/modules/User/pages/NewPasswordView: add types
and update to named export
---
client/modules/User/pages/NewPasswordView.tsx | 10 +++++-----
client/routes.jsx | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/client/modules/User/pages/NewPasswordView.tsx b/client/modules/User/pages/NewPasswordView.tsx
index 2bc310e6a3..e074f9a7a5 100644
--- a/client/modules/User/pages/NewPasswordView.tsx
+++ b/client/modules/User/pages/NewPasswordView.tsx
@@ -8,13 +8,15 @@ import NewPasswordForm from '../components/NewPasswordForm';
import { validateResetPasswordToken } from '../actions';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
+import { RootState } from '../../../reducers';
-function NewPasswordView() {
+export function NewPasswordView() {
const { t } = useTranslation();
- const params = useParams();
+ // eslint-disable-next-line camelcase
+ const params = useParams<{ reset_password_token: string }>();
const resetPasswordToken = params.reset_password_token;
const resetPasswordInvalid = useSelector(
- (state) => state.user.resetPasswordInvalid
+ (state: RootState) => state.user.resetPasswordInvalid
);
const dispatch = useDispatch();
@@ -48,5 +50,3 @@ function NewPasswordView() {
);
}
-
-export default NewPasswordView;
diff --git a/client/routes.jsx b/client/routes.jsx
index 59f5fda169..7cb77811ab 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -14,7 +14,7 @@ import { LoginView } from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
import ResetPasswordView from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
-import NewPasswordView from './modules/User/pages/NewPasswordView';
+import { NewPasswordView } from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView';
import { CollectionView } from './modules/User/pages/CollectionView';
import DashboardView from './modules/User/pages/DashboardView';
From edbf29e2b0e750cdfebdc167799210695c092fb0 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:26:40 +0000
Subject: [PATCH 30/61] client/modules/User/components/ResponseiveForm: delete
unused file
---
.../User/components/ResponsiveForm.jsx | 54 -------------------
1 file changed, 54 deletions(-)
delete mode 100644 client/modules/User/components/ResponsiveForm.jsx
diff --git a/client/modules/User/components/ResponsiveForm.jsx b/client/modules/User/components/ResponsiveForm.jsx
deleted file mode 100644
index 829b83cd14..0000000000
--- a/client/modules/User/components/ResponsiveForm.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import styled from 'styled-components';
-import { remSize } from '../../../theme';
-
-const ResponsiveForm = styled.div`
- .form-container__content {
- width: unset !important;
- padding-top: ${remSize(16)};
- padding-bottom: ${remSize(64)};
- }
-
- .form__input {
- min-width: unset;
- padding: 0px ${remSize(12)};
- height: ${remSize(28)};
- }
- .form-container__title {
- margin-bottom: ${remSize(14)};
- }
- p.form__field {
- margin-top: 0px !important;
- }
- label.form__label {
- margin-top: ${remSize(8)} !important;
- }
-
- .form-error {
- width: 100%;
- }
-
- .nav__items-right:last-child {
- display: none;
- }
-
- .form-container {
- height: 100%;
- }
-
- .nav__dropdown {
- right: 0 !important;
- left: unset !important;
- }
-
- .form-container__stack {
- svg {
- width: ${remSize(12)};
- height: ${remSize(12)};
- }
- a {
- padding: 0px;
- }
- }
-`;
-
-export default ResponsiveForm;
From 70d088fae2cf41bc932e3acc20d25206fe780b74 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 03:16:55 +0000
Subject: [PATCH 31/61] package.json: add react-helmet types
---
package-lock.json | 20 ++++++++++++++++++++
package.json | 2 +-
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 9edffa05e9..f948e6f8dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -171,6 +171,7 @@
"@types/passport": "^1.0.17",
"@types/react": "^16.14.0",
"@types/react-dom": "^16.9.25",
+ "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
@@ -16646,6 +16647,16 @@
"@types/react": "^16.0.0"
}
},
+ "node_modules/@types/react-helmet": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+ "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-redux": {
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
@@ -53346,6 +53357,15 @@
"dev": true,
"requires": {}
},
+ "@types/react-helmet": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+ "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-redux": {
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
diff --git a/package.json b/package.json
index 643d468aa3..df7a8dc603 100644
--- a/package.json
+++ b/package.json
@@ -144,9 +144,9 @@
"@types/nodemailer": "^7.0.1",
"@types/nodemailer-mailgun-transport": "^1.4.6",
"@types/passport": "^1.0.17",
- "@types/passport": "^1.0.17",
"@types/react": "^16.14.0",
"@types/react-dom": "^16.9.25",
+ "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
From 851681a8d3ba7c8c4b324d4b783a0d4afb779cc8 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:29:11 +0000
Subject: [PATCH 32/61] client/modules/User/pages/ResetPasswordView: update to
ts, no-verify
---
.../User/pages/{ResetPasswordView.jsx => ResetPasswordView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{ResetPasswordView.jsx => ResetPasswordView.tsx} (100%)
diff --git a/client/modules/User/pages/ResetPasswordView.jsx b/client/modules/User/pages/ResetPasswordView.tsx
similarity index 100%
rename from client/modules/User/pages/ResetPasswordView.jsx
rename to client/modules/User/pages/ResetPasswordView.tsx
From a647f19a8ad0d67f61d273ba659e918508d16437 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:29:38 +0000
Subject: [PATCH 33/61] client/modules/User/pages/ResetPasswordView: add types
& update to named export
---
client/modules/User/pages/ResetPasswordView.tsx | 7 +++----
client/routes.jsx | 2 +-
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/client/modules/User/pages/ResetPasswordView.tsx b/client/modules/User/pages/ResetPasswordView.tsx
index e744311b7c..fe7a1eeec4 100644
--- a/client/modules/User/pages/ResetPasswordView.tsx
+++ b/client/modules/User/pages/ResetPasswordView.tsx
@@ -7,11 +7,12 @@ import { useTranslation } from 'react-i18next';
import ResetPasswordForm from '../components/ResetPasswordForm';
import { RootPage } from '../../../components/RootPage';
import Nav from '../../IDE/components/Header/Nav';
+import { RootState } from '../../../reducers';
-function ResetPasswordView() {
+export function ResetPasswordView() {
const { t } = useTranslation();
const resetPasswordInitiate = useSelector(
- (state) => state.user.resetPasswordInitiate
+ (state: RootState) => state.user.resetPasswordInitiate
);
const resetPasswordClass = classNames({
'reset-password': true,
@@ -48,5 +49,3 @@ function ResetPasswordView() {
);
}
-
-export default ResetPasswordView;
diff --git a/client/routes.jsx b/client/routes.jsx
index 7cb77811ab..e07cec91f7 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -12,7 +12,7 @@ import { PrivacyPolicy } from './modules/Legal/pages/PrivacyPolicy';
import { TermsOfUse } from './modules/Legal/pages/TermsOfUse';
import { LoginView } from './modules/User/pages/LoginView';
import SignupView from './modules/User/pages/SignupView';
-import ResetPasswordView from './modules/User/pages/ResetPasswordView';
+import { ResetPasswordView } from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
import { NewPasswordView } from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView';
From 9b49e6889922223aa50134dc8bde721e3b2f0962 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:34:22 +0000
Subject: [PATCH 34/61] client/modules/User/components/NewPasswordForm: update
to ts, no-verify
---
.../User/components/{NewPasswordForm.jsx => NewPasswordForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{NewPasswordForm.jsx => NewPasswordForm.tsx} (100%)
diff --git a/client/modules/User/components/NewPasswordForm.jsx b/client/modules/User/components/NewPasswordForm.tsx
similarity index 100%
rename from client/modules/User/components/NewPasswordForm.jsx
rename to client/modules/User/components/NewPasswordForm.tsx
From 8d0fe3ecb0b31083515a6eaf15ffe22dd170ab36 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:35:50 +0000
Subject: [PATCH 35/61] client/modules/User/components/NewPasswordForm: add
types & update to named export
---
client/modules/User/components/NewPasswordForm.tsx | 12 +++---------
.../User/components/NewPasswordForm.unit.test.jsx | 2 +-
client/modules/User/pages/NewPasswordView.tsx | 2 +-
3 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/client/modules/User/components/NewPasswordForm.tsx b/client/modules/User/components/NewPasswordForm.tsx
index feca326c77..9d08f678c5 100644
--- a/client/modules/User/components/NewPasswordForm.tsx
+++ b/client/modules/User/components/NewPasswordForm.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
import React from 'react';
import { Form, Field } from 'react-final-form';
import { useDispatch } from 'react-redux';
@@ -6,13 +5,14 @@ import { useTranslation } from 'react-i18next';
import { validateNewPassword } from '../../../utils/reduxFormUtils';
import { updatePassword } from '../actions';
import { Button, ButtonTypes } from '../../../common/Button';
+import type { NewPasswordForm as NewPasswordFormType } from '../../../utils/reduxFormUtils';
-function NewPasswordForm(props) {
+export function NewPasswordForm(props: { resetPasswordToken: string }) {
const { resetPasswordToken } = props;
const { t } = useTranslation();
const dispatch = useDispatch();
- function onSubmit(formProps) {
+ function onSubmit(formProps: NewPasswordFormType) {
return dispatch(updatePassword(formProps, resetPasswordToken));
}
@@ -75,9 +75,3 @@ function NewPasswordForm(props) {
);
}
-
-NewPasswordForm.propTypes = {
- resetPasswordToken: PropTypes.string.isRequired
-};
-
-export default NewPasswordForm;
diff --git a/client/modules/User/components/NewPasswordForm.unit.test.jsx b/client/modules/User/components/NewPasswordForm.unit.test.jsx
index dbddf3c8cb..a55af924ed 100644
--- a/client/modules/User/components/NewPasswordForm.unit.test.jsx
+++ b/client/modules/User/components/NewPasswordForm.unit.test.jsx
@@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store';
import { fireEvent } from '@storybook/testing-library';
import { reduxRender, screen, act, waitFor } from '../../../test-utils';
import { initialTestState } from '../../../testData/testReduxStore';
-import NewPasswordForm from './NewPasswordForm';
+import { NewPasswordForm } from './NewPasswordForm';
const mockStore = configureStore([thunk]);
const store = mockStore(initialTestState);
diff --git a/client/modules/User/pages/NewPasswordView.tsx b/client/modules/User/pages/NewPasswordView.tsx
index e074f9a7a5..1433624df0 100644
--- a/client/modules/User/pages/NewPasswordView.tsx
+++ b/client/modules/User/pages/NewPasswordView.tsx
@@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
-import NewPasswordForm from '../components/NewPasswordForm';
+import { NewPasswordForm } from '../components/NewPasswordForm';
import { validateResetPasswordToken } from '../actions';
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
From 8226165b68ffed6fe087999a5acca01f291546b3 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:37:25 +0000
Subject: [PATCH 36/61] client/modules/User/pages/SignupView: update to ts,
no-verify
---
client/modules/User/pages/{SignupView.jsx => SignupView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{SignupView.jsx => SignupView.tsx} (100%)
diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.tsx
similarity index 100%
rename from client/modules/User/pages/SignupView.jsx
rename to client/modules/User/pages/SignupView.tsx
From fd922368ce6376f0825882e459de48fe10063f8e Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:38:13 +0000
Subject: [PATCH 37/61] client/modules/User/pages/SignupView: update to named
export
---
client/modules/User/pages/SignupView.tsx | 4 +---
client/routes.jsx | 2 +-
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/client/modules/User/pages/SignupView.tsx b/client/modules/User/pages/SignupView.tsx
index 3248a7bc61..50b54555c4 100644
--- a/client/modules/User/pages/SignupView.tsx
+++ b/client/modules/User/pages/SignupView.tsx
@@ -10,7 +10,7 @@ import {
import Nav from '../../IDE/components/Header/Nav';
import { RootPage } from '../../../components/RootPage';
-function SignupView() {
+export function SignupView() {
const { t } = useTranslation();
return (
@@ -49,5 +49,3 @@ function SignupView() {
);
}
-
-export default SignupView;
diff --git a/client/routes.jsx b/client/routes.jsx
index e07cec91f7..4e399561ff 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -11,7 +11,7 @@ import { CodeOfConduct } from './modules/Legal/pages/CodeOfConduct';
import { PrivacyPolicy } from './modules/Legal/pages/PrivacyPolicy';
import { TermsOfUse } from './modules/Legal/pages/TermsOfUse';
import { LoginView } from './modules/User/pages/LoginView';
-import SignupView from './modules/User/pages/SignupView';
+import { SignupView } from './modules/User/pages/SignupView';
import { ResetPasswordView } from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
import { NewPasswordView } from './modules/User/pages/NewPasswordView';
From ad990c0c21beb793bf121845946ac75968b5a22a Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:39:50 +0000
Subject: [PATCH 38/61] client/modules/User/components/ResetPasswordForm:
update to ts, no-verify
---
.../components/{ResetPasswordForm.jsx => ResetPasswordForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{ResetPasswordForm.jsx => ResetPasswordForm.tsx} (100%)
diff --git a/client/modules/User/components/ResetPasswordForm.jsx b/client/modules/User/components/ResetPasswordForm.tsx
similarity index 100%
rename from client/modules/User/components/ResetPasswordForm.jsx
rename to client/modules/User/components/ResetPasswordForm.tsx
From c58e78be2a2c3afa59cd7496af852f43f93e5ebe Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:41:42 +0000
Subject: [PATCH 39/61] client/modules/User/components/ResetPasswordForm:
update named export & add types
---
client/modules/User/components/ResetPasswordForm.tsx | 10 +++++-----
client/modules/User/pages/ResetPasswordView.tsx | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/client/modules/User/components/ResetPasswordForm.tsx b/client/modules/User/components/ResetPasswordForm.tsx
index fe3752fdf4..f3668b9636 100644
--- a/client/modules/User/components/ResetPasswordForm.tsx
+++ b/client/modules/User/components/ResetPasswordForm.tsx
@@ -5,15 +5,17 @@ import { useDispatch, useSelector } from 'react-redux';
import { validateResetPassword } from '../../../utils/reduxFormUtils';
import { initiateResetPassword } from '../actions';
import { Button, ButtonTypes } from '../../../common/Button';
+import { RootState } from '../../../reducers';
+import { ResetPasswordInitiateRequestBody } from '../../../../common/types';
-function ResetPasswordForm(props) {
+export function ResetPasswordForm() {
const { t } = useTranslation();
const resetPasswordInitiate = useSelector(
- (state) => state.user.resetPasswordInitiate
+ (state: RootState) => state.user.resetPasswordInitiate
);
const dispatch = useDispatch();
- function onSubmit(formProps) {
+ function onSubmit(formProps: ResetPasswordInitiateRequestBody) {
dispatch(initiateResetPassword(formProps));
}
@@ -57,5 +59,3 @@ function ResetPasswordForm(props) {
);
}
-
-export default ResetPasswordForm;
diff --git a/client/modules/User/pages/ResetPasswordView.tsx b/client/modules/User/pages/ResetPasswordView.tsx
index fe7a1eeec4..09916047e4 100644
--- a/client/modules/User/pages/ResetPasswordView.tsx
+++ b/client/modules/User/pages/ResetPasswordView.tsx
@@ -4,7 +4,7 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
-import ResetPasswordForm from '../components/ResetPasswordForm';
+import { ResetPasswordForm } from '../components/ResetPasswordForm';
import { RootPage } from '../../../components/RootPage';
import Nav from '../../IDE/components/Header/Nav';
import { RootState } from '../../../reducers';
From b8e7361f81a1c113c87d01e7dd594a248c6ae7fb Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:58:48 +0000
Subject: [PATCH 40/61] client/modules/User/components/SignupForm: update to
ts, no-verify
---
client/modules/User/components/{SignupForm.jsx => SignupForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{SignupForm.jsx => SignupForm.tsx} (100%)
diff --git a/client/modules/User/components/SignupForm.jsx b/client/modules/User/components/SignupForm.tsx
similarity index 100%
rename from client/modules/User/components/SignupForm.jsx
rename to client/modules/User/components/SignupForm.tsx
From 023096bc4671e4a57bd4c2d8cef5cd8f4bff0f68 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 23:59:40 +0000
Subject: [PATCH 41/61] client/modules/User/components/SignupForm: add types
and update to named export; update useSyncFormTranslations FormlikeType to
take generic
---
client/common/useSyncFormTranslations.ts | 22 ++++++++-------
client/modules/User/components/LoginForm.tsx | 2 +-
client/modules/User/components/SignupForm.tsx | 27 +++++++++++--------
client/modules/User/pages/SignupView.tsx | 2 +-
4 files changed, 30 insertions(+), 23 deletions(-)
diff --git a/client/common/useSyncFormTranslations.ts b/client/common/useSyncFormTranslations.ts
index aed766aa43..c116db1e04 100644
--- a/client/common/useSyncFormTranslations.ts
+++ b/client/common/useSyncFormTranslations.ts
@@ -1,18 +1,19 @@
import { useEffect, MutableRefObject } from 'react';
+import type { FormApi } from 'final-form';
-export interface FormLike {
- getState(): { values: Record };
- reset(): void;
- change(field: string, value: unknown): void;
-}
+// Generic FormLike that mirrors FormApi for any form value type
+export type FormLike> = Pick<
+ FormApi,
+ 'getState' | 'reset' | 'change'
+>;
/**
* This hook ensures that form values are preserved when the language changes.
* @param formRef
* @param language
*/
-export const useSyncFormTranslations = (
- formRef: MutableRefObject,
+export const useSyncFormTranslations = >(
+ formRef: MutableRefObject | null>,
language: string
) => {
useEffect(() => {
@@ -22,9 +23,10 @@ export const useSyncFormTranslations = (
const { values } = form.getState();
form.reset();
- Object.keys(values).forEach((field) => {
- if (values[field]) {
- form.change(field, values[field]);
+ (Object.keys(values) as (keyof FormValues)[]).forEach((field) => {
+ const value = values[field];
+ if (value !== undefined && value !== null && value !== '') {
+ form.change(field, value);
}
});
}, [language]);
diff --git a/client/modules/User/components/LoginForm.tsx b/client/modules/User/components/LoginForm.tsx
index 17bdaf9393..c3741ffce7 100644
--- a/client/modules/User/components/LoginForm.tsx
+++ b/client/modules/User/components/LoginForm.tsx
@@ -20,7 +20,7 @@ export function LoginForm() {
return dispatch(validateAndLoginUser(formProps));
}
const [showPassword, setShowPassword] = useState(false);
- const formRef = useRef(null);
+ const formRef = useRef | null>(null);
const handleVisibility = () => {
setShowPassword(!showPassword);
diff --git a/client/modules/User/components/SignupForm.tsx b/client/modules/User/components/SignupForm.tsx
index 45d44da875..dbbac68932 100644
--- a/client/modules/User/components/SignupForm.tsx
+++ b/client/modules/User/components/SignupForm.tsx
@@ -7,16 +7,23 @@ import { validateSignup } from '../../../utils/reduxFormUtils';
import { validateAndSignUpUser } from '../actions';
import { Button, ButtonTypes } from '../../../common/Button';
import { apiClient } from '../../../utils/apiClient';
-import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations';
+import {
+ FormLike,
+ useSyncFormTranslations
+} from '../../../common/useSyncFormTranslations';
+import {
+ CreateUserRequestBody,
+ DuplicateUserCheckQuery
+} from '../../../../common/types';
-const timeoutRef = { current: null };
+const timeoutRef: { current: (() => void) | null } = { current: null };
-function asyncValidate(fieldToValidate, value) {
+function asyncValidate(fieldToValidate: 'username' | 'email', value: string) {
if (!value || value.trim().length === 0) {
return Promise.resolve('');
}
- const queryParams = {
+ const queryParams: DuplicateUserCheckQuery = {
[fieldToValidate]: value,
check_type: fieldToValidate
};
@@ -48,18 +55,18 @@ function asyncValidate(fieldToValidate, value) {
});
}
-function validateUsername(username) {
+function validateUsername(username: CreateUserRequestBody['username']) {
return asyncValidate('username', username);
}
-function validateEmail(email) {
+function validateEmail(email: CreateUserRequestBody['email']) {
return asyncValidate('email', email);
}
-function SignupForm() {
+export function SignupForm() {
const { t, i18n } = useTranslation();
const dispatch = useDispatch();
- const formRef = useRef(null);
+ const formRef = useRef | null>(null);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
@@ -69,7 +76,7 @@ function SignupForm() {
const handleConfirmVisibility = () =>
setShowConfirmPassword(!showConfirmPassword);
- function onSubmit(formProps) {
+ function onSubmit(formProps: CreateUserRequestBody) {
return dispatch(validateAndSignUpUser(formProps));
}
@@ -216,5 +223,3 @@ function SignupForm() {
);
}
-
-export default SignupForm;
diff --git a/client/modules/User/pages/SignupView.tsx b/client/modules/User/pages/SignupView.tsx
index 50b54555c4..36006d51ef 100644
--- a/client/modules/User/pages/SignupView.tsx
+++ b/client/modules/User/pages/SignupView.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { useTranslation, Trans } from 'react-i18next';
-import SignupForm from '../components/SignupForm';
+import { SignupForm } from '../components/SignupForm';
import {
SocialAuthButton,
SocialAuthServices
From 248e2c201fc6d3c2fc3f4ab4aca150b860858cd7 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:14:30 +0000
Subject: [PATCH 42/61] client/modules/User/components/CookieConsent: update to
ts, no-verify
---
.../User/components/{CookieConsent.jsx => CookieConsent.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{CookieConsent.jsx => CookieConsent.tsx} (100%)
diff --git a/client/modules/User/components/CookieConsent.jsx b/client/modules/User/components/CookieConsent.tsx
similarity index 100%
rename from client/modules/User/components/CookieConsent.jsx
rename to client/modules/User/components/CookieConsent.tsx
From ebe82159ad5df697d33f5b99c86e21cec8ae95ea Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:16:31 +0000
Subject: [PATCH 43/61] client/modules/User/components/CookieConsent: add type
dependencies, update to named export, update types
---
client/modules/App/App.jsx | 2 +-
.../modules/User/components/CookieConsent.tsx | 84 ++++++++++++-------
package-lock.json | 32 +++++++
package.json | 2 +
4 files changed, 89 insertions(+), 31 deletions(-)
diff --git a/client/modules/App/App.jsx b/client/modules/App/App.jsx
index 53b0c82c63..242b5244c8 100644
--- a/client/modules/App/App.jsx
+++ b/client/modules/App/App.jsx
@@ -6,7 +6,7 @@ import { showReduxDevTools } from '../../store';
import DevTools from './components/DevTools';
import { setPreviousPath } from '../IDE/actions/ide';
import { setLanguage } from '../IDE/actions/preferences';
-import CookieConsent from '../User/components/CookieConsent';
+import { CookieConsent } from '../User/components/CookieConsent';
function hideCookieConsent(pathname) {
if (pathname.includes('/full/') || pathname.includes('/embed/')) {
diff --git a/client/modules/User/components/CookieConsent.tsx b/client/modules/User/components/CookieConsent.tsx
index 9cfff74ac6..ad8fa8e743 100644
--- a/client/modules/User/components/CookieConsent.tsx
+++ b/client/modules/User/components/CookieConsent.tsx
@@ -6,17 +6,21 @@ import ReactGA from 'react-ga';
import { Transition } from 'react-transition-group';
import { Link } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
-import PropTypes from 'prop-types';
import { getConfig } from '../../../utils/getConfig';
import { setUserCookieConsent } from '../actions';
import { remSize, prop, device } from '../../../theme';
import { Button, ButtonKinds } from '../../../common/Button';
+import { RootState } from '../../../reducers';
+import { CookieConsentOptions } from '../../../../common/types';
+interface CookieConsentContainerState {
+ state: string;
+}
const CookieConsentContainer = styled.div`
position: fixed;
transition: 1.6s cubic-bezier(0.165, 0.84, 0.44, 1);
bottom: 0;
- transform: ${({ state }) => {
+ transform: ${({ state }: CookieConsentContainerState) => {
if (state === 'entered') {
return 'translateY(0)';
}
@@ -79,37 +83,49 @@ const CookieConsentButtons = styled.div`
const GOOGLE_ANALYTICS_ID = getConfig('GA_MEASUREMENT_ID');
-function CookieConsent({ hide }) {
- const user = useSelector((state) => state.user);
- const [cookieConsent, setBrowserCookieConsent] = useState('none');
+export function CookieConsent({ hide = false }: { hide?: boolean }) {
+ const user = useSelector((state: RootState) => state.user);
+ const [
+ cookieConsent,
+ setBrowserCookieConsent
+ ] = useState(CookieConsentOptions.NONE);
const [inProp, setInProp] = useState(false);
const dispatch = useDispatch();
const { t } = useTranslation();
function initializeCookieConsent() {
if (user.authenticated) {
+ if (!user.cookieConsent) {
+ return;
+ }
setBrowserCookieConsent(user.cookieConsent);
Cookies.set('p5-cookie-consent', user.cookieConsent, { expires: 365 });
return;
}
- setBrowserCookieConsent('none');
- Cookies.set('p5-cookie-consent', 'none', { expires: 365 });
+ setBrowserCookieConsent(CookieConsentOptions.NONE);
+ Cookies.set('p5-cookie-consent', CookieConsentOptions.NONE, {
+ expires: 365
+ });
}
function acceptAllCookies() {
if (user.authenticated) {
- dispatch(setUserCookieConsent('all'));
+ dispatch(setUserCookieConsent(CookieConsentOptions.ALL));
}
- setBrowserCookieConsent('all');
- Cookies.set('p5-cookie-consent', 'all', { expires: 365 });
+ setBrowserCookieConsent(CookieConsentOptions.ALL);
+ Cookies.set('p5-cookie-consent', CookieConsentOptions.ALL, {
+ expires: 365
+ });
}
function acceptEssentialCookies() {
if (user.authenticated) {
- dispatch(setUserCookieConsent('essential'));
+ dispatch(setUserCookieConsent(CookieConsentOptions.ESSENTIAL));
}
- setBrowserCookieConsent('essential');
- Cookies.set('p5-cookie-consent', 'essential', { expires: 365 });
+ setBrowserCookieConsent(CookieConsentOptions.ESSENTIAL);
+ Cookies.set('p5-cookie-consent', CookieConsentOptions.ESSENTIAL, {
+ expires: 365
+ });
// Remove Google Analytics Cookies
Cookies.remove('_ga');
Cookies.remove('_gat');
@@ -118,11 +134,29 @@ function CookieConsent({ hide }) {
function mergeCookieConsent() {
if (user.authenticated) {
- if (user.cookieConsent === 'none' && cookieConsent !== 'none') {
- dispatch(setUserCookieConsent(cookieConsent));
- } else if (user.cookieConsent !== 'none') {
+ if (!user.cookieConsent) {
+ return;
+ }
+ if (
+ ![
+ CookieConsentOptions.ALL,
+ CookieConsentOptions.ESSENTIAL,
+ CookieConsentOptions.NONE
+ ].includes(user.cookieConsent)
+ ) {
+ return;
+ }
+
+ if (
+ user.cookieConsent === CookieConsentOptions.NONE &&
+ cookieConsent !== CookieConsentOptions.NONE
+ ) {
+ dispatch(setUserCookieConsent(cookieConsent as CookieConsentOptions));
+ } else if (user.cookieConsent !== CookieConsentOptions.NONE) {
setBrowserCookieConsent(user.cookieConsent);
- Cookies.set('p5-cookie-consent', user.cookieConsent, { expires: 365 });
+ Cookies.set('p5-cookie-consent', user.cookieConsent, {
+ expires: 365
+ });
}
}
}
@@ -130,7 +164,7 @@ function CookieConsent({ hide }) {
useEffect(() => {
const p5CookieConsent = Cookies.get('p5-cookie-consent');
if (p5CookieConsent) {
- setBrowserCookieConsent(p5CookieConsent);
+ setBrowserCookieConsent(p5CookieConsent as CookieConsentOptions);
} else {
initializeCookieConsent();
}
@@ -165,9 +199,9 @@ function CookieConsent({ hide }) {
return (
- {(state) => (
+ {(state: CookieConsentContainerState['state']) => (
-
+
{t('Cookies.Header')}
@@ -191,13 +225,3 @@ function CookieConsent({ hide }) {
);
}
-
-CookieConsent.propTypes = {
- hide: PropTypes.bool
-};
-
-CookieConsent.defaultProps = {
- hide: false
-};
-
-export default CookieConsent;
diff --git a/package-lock.json b/package-lock.json
index f948e6f8dd..f07e6ee670 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -164,6 +164,7 @@
"@types/classnames": "^2.3.0",
"@types/friendly-words": "^1.2.2",
"@types/jest": "^29.5.14",
+ "@types/js-cookie": "^3.0.6",
"@types/mjml": "^4.7.4",
"@types/node": "^16.18.126",
"@types/nodemailer": "^7.0.1",
@@ -173,6 +174,7 @@
"@types/react-dom": "^16.9.25",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -16448,6 +16450,13 @@
"pretty-format": "^29.0.0"
}
},
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/js-levenshtein": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -16691,6 +16700,16 @@
"@types/react-router": "*"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react/node_modules/csstype": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
@@ -53160,6 +53179,12 @@
"pretty-format": "^29.0.0"
}
},
+ "@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true
+ },
"@types/js-levenshtein": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -53398,6 +53423,13 @@
"@types/react-router": "*"
}
},
+ "@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "dev": true,
+ "requires": {}
+ },
"@types/redux-devtools-themes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz",
diff --git a/package.json b/package.json
index df7a8dc603..1f531d396f 100644
--- a/package.json
+++ b/package.json
@@ -139,6 +139,7 @@
"@types/classnames": "^2.3.0",
"@types/friendly-words": "^1.2.2",
"@types/jest": "^29.5.14",
+ "@types/js-cookie": "^3.0.6",
"@types/mjml": "^4.7.4",
"@types/node": "^16.18.126",
"@types/nodemailer": "^7.0.1",
@@ -148,6 +149,7 @@
"@types/react-dom": "^16.9.25",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^5.62.0",
From 57ab54016d7e229af22d55131ef90a2ec8a351f7 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:39:03 +0000
Subject: [PATCH 44/61] client/modules/User/components/VisibilityDropdown:
update to ts, no-verify
---
.../components/{VisibilityDropdown.jsx => VisibilityDropdown.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{VisibilityDropdown.jsx => VisibilityDropdown.tsx} (100%)
diff --git a/client/modules/User/components/VisibilityDropdown.jsx b/client/modules/User/components/VisibilityDropdown.tsx
similarity index 100%
rename from client/modules/User/components/VisibilityDropdown.jsx
rename to client/modules/User/components/VisibilityDropdown.tsx
From 3e93724d9fc0c62064d0f32824d527fe99977f54 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:39:23 +0000
Subject: [PATCH 45/61] client/modules/User/components/VisibilityDropdown:
update to named export and add types
---
.../modules/IDE/components/Header/Toolbar.jsx | 2 +-
.../IDE/components/SketchListRowBase.jsx | 2 +-
.../User/components/VisibilityDropdown.tsx | 54 +++++++++++--------
3 files changed, 33 insertions(+), 25 deletions(-)
diff --git a/client/modules/IDE/components/Header/Toolbar.jsx b/client/modules/IDE/components/Header/Toolbar.jsx
index fbf0b5d091..16ed74ff3e 100644
--- a/client/modules/IDE/components/Header/Toolbar.jsx
+++ b/client/modules/IDE/components/Header/Toolbar.jsx
@@ -20,7 +20,7 @@ import StopIcon from '../../../../images/stop.svg';
import PreferencesIcon from '../../../../images/preferences.svg';
import ProjectName from './ProjectName';
import VersionIndicator from '../VersionIndicator';
-import VisibilityDropdown from '../../../User/components/VisibilityDropdown';
+import { VisibilityDropdown } from '../../../User/components/VisibilityDropdown';
import { changeVisibility } from '../../actions/project';
const Toolbar = (props) => {
diff --git a/client/modules/IDE/components/SketchListRowBase.jsx b/client/modules/IDE/components/SketchListRowBase.jsx
index e1c4c80f13..d11cb8bf40 100644
--- a/client/modules/IDE/components/SketchListRowBase.jsx
+++ b/client/modules/IDE/components/SketchListRowBase.jsx
@@ -10,7 +10,7 @@ import { TableDropdown } from '../../../components/Dropdown/TableDropdown';
import { MenuItem } from '../../../components/Dropdown/MenuItem';
import { formatDateToString } from '../../../utils/formatDate';
import { getConfig } from '../../../utils/getConfig';
-import VisibilityDropdown from '../../User/components/VisibilityDropdown';
+import { VisibilityDropdown } from '../../User/components/VisibilityDropdown';
const ROOT_URL = getConfig('API_URL');
diff --git a/client/modules/User/components/VisibilityDropdown.tsx b/client/modules/User/components/VisibilityDropdown.tsx
index 972ccad88a..d5ea66f234 100644
--- a/client/modules/User/components/VisibilityDropdown.tsx
+++ b/client/modules/User/components/VisibilityDropdown.tsx
@@ -1,13 +1,29 @@
import React, { useState, useRef, useEffect } from 'react';
-import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import LockIcon from '../../../images/lock.svg';
import EarthIcon from '../../../images/earth.svg';
import CheckmarkIcon from '../../../images/checkmark.svg';
-const VisibilityDropdown = ({ sketch, onVisibilityChange, location }) => {
+export interface VisibilityDropdownProps {
+ sketch: {
+ id: string;
+ name: string;
+ visibility: string;
+ };
+ onVisibilityChange: (
+ sketchId: string,
+ sketchName: string,
+ newVisibility: string
+ ) => void;
+ location?: string;
+}
+export const VisibilityDropdown = ({
+ sketch,
+ onVisibilityChange,
+ location = 'sketchlist'
+}: VisibilityDropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
- const dropdownRef = useRef(null);
+ const dropdownRef = useRef(null);
const { t } = useTranslation();
@@ -31,8 +47,13 @@ const VisibilityDropdown = ({ sketch, onVisibilityChange, location }) => {
visibilityOptions[0];
useEffect(() => {
- const handleClickOutside = (event) => {
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
+ const handleClickOutside = (event: MouseEvent) => {
+ const target = event.target as Node | null;
+ if (
+ dropdownRef.current &&
+ target &&
+ !dropdownRef.current.contains(target)
+ ) {
setIsOpen(false);
}
};
@@ -41,14 +62,17 @@ const VisibilityDropdown = ({ sketch, onVisibilityChange, location }) => {
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
- const handleVisibilitySelect = (newVisibility) => {
+ const handleVisibilitySelect = (newVisibility: string) => {
if (newVisibility !== sketch.visibility) {
onVisibilityChange(sketch.id, sketch.name, newVisibility);
}
setIsOpen(false);
};
- const handleKeyDown = (event, visibility) => {
+ const handleKeyDown = (
+ event: React.KeyboardEvent,
+ visibility: string
+ ) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleVisibilitySelect(visibility);
@@ -104,19 +128,3 @@ const VisibilityDropdown = ({ sketch, onVisibilityChange, location }) => {
);
};
-
-VisibilityDropdown.defaultProps = {
- location: 'sketchlist'
-};
-
-VisibilityDropdown.propTypes = {
- sketch: PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- visibility: PropTypes.string.isRequired
- }).isRequired,
- onVisibilityChange: PropTypes.func.isRequired,
- location: PropTypes.string
-};
-
-export default VisibilityDropdown;
From 730270148b332ab9a9af072cd5ff599816d0818e Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:50:04 +0000
Subject: [PATCH 46/61] client/modules/User/components/DashboardTabSwitcher:
update to ts, no-verify
---
.../{DashboardTabSwitcher.jsx => DashboardTabSwitcher.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{DashboardTabSwitcher.jsx => DashboardTabSwitcher.tsx} (100%)
diff --git a/client/modules/User/components/DashboardTabSwitcher.jsx b/client/modules/User/components/DashboardTabSwitcher.tsx
similarity index 100%
rename from client/modules/User/components/DashboardTabSwitcher.jsx
rename to client/modules/User/components/DashboardTabSwitcher.tsx
From 239b7f325acb446428550fe041429527e6cb2638 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 00:50:19 +0000
Subject: [PATCH 47/61] client/modules/User/components/DashboardTabSwitcher:
update to named export, add types
---
.../User/components/DashboardTabSwitcher.tsx | 29 ++++++++++---------
client/modules/User/pages/DashboardView.jsx | 5 ++--
2 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/client/modules/User/components/DashboardTabSwitcher.tsx b/client/modules/User/components/DashboardTabSwitcher.tsx
index 7afbe5aec8..d494bf6045 100644
--- a/client/modules/User/components/DashboardTabSwitcher.tsx
+++ b/client/modules/User/components/DashboardTabSwitcher.tsx
@@ -10,11 +10,11 @@ import { Options } from '../../IDE/components/Header/MobileNav';
import { toggleDirectionForField } from '../../IDE/actions/sorting';
import useIsMobile from '../../IDE/hooks/useIsMobile';
-export const TabKey = {
- assets: 'assets',
- collections: 'collections',
- sketches: 'sketches'
-};
+export enum TabKey {
+ assets = 'assets',
+ collections = 'collections',
+ sketches = 'sketches'
+}
// It is good for right now, because we need to separate the nav dropdown logic from the navBar before we can use it here
const FilterOptions = styled(Options)`
@@ -25,7 +25,16 @@ const FilterOptions = styled(Options)`
}
`;
-const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
+export interface DashboardTabSwitcherProps {
+ currentTab: TabKey;
+ isOwner: string;
+ username: string;
+}
+export const DashboardTabSwitcher = ({
+ currentTab,
+ isOwner,
+ username
+}: DashboardTabSwitcherProps) => {
const isMobile = useIsMobile();
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -89,11 +98,3 @@ const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => {
);
};
-
-DashboardTabSwitcher.propTypes = {
- currentTab: PropTypes.string.isRequired,
- isOwner: PropTypes.bool.isRequired,
- username: PropTypes.string.isRequired
-};
-
-export default DashboardTabSwitcher;
diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx
index 1626d0d369..8044de028a 100644
--- a/client/modules/User/pages/DashboardView.jsx
+++ b/client/modules/User/pages/DashboardView.jsx
@@ -18,7 +18,8 @@ import {
} from '../../IDE/components/Searchbar';
import CollectionCreate from '../components/CollectionCreate';
-import DashboardTabSwitcherPublic, {
+import {
+ DashboardTabSwitcher,
TabKey
} from '../components/DashboardTabSwitcher';
import useIsMobile from '../../IDE/hooks/useIsMobile';
@@ -123,7 +124,7 @@ const DashboardView = () => {
{ownerName()}
-
Date: Mon, 27 Oct 2025 01:13:18 +0000
Subject: [PATCH 48/61] CollectionShareButton: update to ts, no-verify
---
.../{CollectionShareButton.jsx => CollectionShareButton.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{CollectionShareButton.jsx => CollectionShareButton.tsx} (100%)
diff --git a/client/modules/User/components/CollectionShareButton.jsx b/client/modules/User/components/CollectionShareButton.tsx
similarity index 100%
rename from client/modules/User/components/CollectionShareButton.jsx
rename to client/modules/User/components/CollectionShareButton.tsx
From 1468ad2ae13356a4e232427cc0534a36fba165c4 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:13:42 +0000
Subject: [PATCH 49/61] CollectionShareButton: update to named export, add ref
type
---
client/modules/User/components/CollectionMetadata.jsx | 2 +-
.../modules/User/components/CollectionShareButton.tsx | 10 ++--------
2 files changed, 3 insertions(+), 9 deletions(-)
diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx
index dcb47e0929..4e98bf323b 100644
--- a/client/modules/User/components/CollectionMetadata.jsx
+++ b/client/modules/User/components/CollectionMetadata.jsx
@@ -11,7 +11,7 @@ import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketc
import EditableInput from '../../IDE/components/EditableInput';
import { SketchSearchbar } from '../../IDE/components/Searchbar';
import { getCollection } from '../../IDE/selectors/collections';
-import ShareURL from './CollectionShareButton';
+import { ShareURL } from './CollectionShareButton';
function CollectionMetadata({ collectionId }) {
const { t } = useTranslation();
diff --git a/client/modules/User/components/CollectionShareButton.tsx b/client/modules/User/components/CollectionShareButton.tsx
index a5d8705dcd..2865773125 100644
--- a/client/modules/User/components/CollectionShareButton.tsx
+++ b/client/modules/User/components/CollectionShareButton.tsx
@@ -7,11 +7,11 @@ import { DropdownArrowIcon } from '../../../common/icons';
import { useModalClose } from '../../../common/useModalClose';
import CopyableInput from '../../IDE/components/CopyableInput';
-const ShareURL = ({ value }) => {
+export const ShareURL = ({ value }: { value: string }) => {
const [showURL, setShowURL] = useState(false);
const { t } = useTranslation();
const close = useCallback(() => setShowURL(false), [setShowURL]);
- const ref = useModalClose(close);
+ const ref = useModalClose(close);
return (
@@ -29,9 +29,3 @@ const ShareURL = ({ value }) => {
);
};
-
-ShareURL.propTypes = {
- value: PropTypes.string.isRequired
-};
-
-export default ShareURL;
From 8b78e7069f20d154a232696cf793c8b77e7d89a4 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:18:17 +0000
Subject: [PATCH 50/61] clean up stray leftover PropTypes
---
client/modules/User/components/CollectionShareButton.tsx | 2 --
client/modules/User/components/DashboardTabSwitcher.tsx | 1 -
client/modules/User/components/SocialAuthButton.tsx | 1 -
3 files changed, 4 deletions(-)
diff --git a/client/modules/User/components/CollectionShareButton.tsx b/client/modules/User/components/CollectionShareButton.tsx
index 2865773125..d8e20bfbd9 100644
--- a/client/modules/User/components/CollectionShareButton.tsx
+++ b/client/modules/User/components/CollectionShareButton.tsx
@@ -1,7 +1,5 @@
-import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
-
import { Button } from '../../../common/Button';
import { DropdownArrowIcon } from '../../../common/icons';
import { useModalClose } from '../../../common/useModalClose';
diff --git a/client/modules/User/components/DashboardTabSwitcher.tsx b/client/modules/User/components/DashboardTabSwitcher.tsx
index d494bf6045..8ec810cf64 100644
--- a/client/modules/User/components/DashboardTabSwitcher.tsx
+++ b/client/modules/User/components/DashboardTabSwitcher.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
diff --git a/client/modules/User/components/SocialAuthButton.tsx b/client/modules/User/components/SocialAuthButton.tsx
index 56ddc9ae5d..b728168b94 100644
--- a/client/modules/User/components/SocialAuthButton.tsx
+++ b/client/modules/User/components/SocialAuthButton.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
From 16fc4389860c866ae06bfb4306908ac349f509b1 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:34:28 +0000
Subject: [PATCH 51/61] client/modules/User/pages/AccountView: update to ts,
no-verify
---
client/modules/User/pages/{AccountView.jsx => AccountView.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/pages/{AccountView.jsx => AccountView.tsx} (100%)
diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.tsx
similarity index 100%
rename from client/modules/User/pages/AccountView.jsx
rename to client/modules/User/pages/AccountView.tsx
From dc98920fefb0f66266ecfed308e94fe5b6d7cd9e Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:43:38 +0000
Subject: [PATCH 52/61] client/modules/User/pages/AccountView: update to named
export, resolve typeerrors
---
client/modules/User/pages/AccountView.tsx | 22 +++++++++++++---------
client/routes.jsx | 2 +-
package-lock.json | 20 ++++++++++++++++++++
package.json | 1 +
4 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/client/modules/User/pages/AccountView.tsx b/client/modules/User/pages/AccountView.tsx
index 508b65816d..4e3fbc8ab2 100644
--- a/client/modules/User/pages/AccountView.tsx
+++ b/client/modules/User/pages/AccountView.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { useSelector } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
@@ -13,13 +13,15 @@ import {
import APIKeyForm from '../components/APIKeyForm';
import Nav from '../../IDE/components/Header/Nav';
import ErrorModal from '../../IDE/components/ErrorModal';
+import { hideErrorModal } from '../../IDE/actions/ide';
import Overlay from '../../App/components/Overlay';
import Toast from '../../IDE/components/Toast';
+import { RootState } from '../../../reducers';
function SocialLoginPanel() {
const { t } = useTranslation();
- const isGithub = useSelector((state) => !!state.user.github);
- const isGoogle = useSelector((state) => !!state.user.google);
+ const isGithub = useSelector((state: RootState) => !!state.user.github);
+ const isGoogle = useSelector((state: RootState) => !!state.user.google);
return (
@@ -45,13 +47,13 @@ function SocialLoginPanel() {
);
}
-function AccountView() {
+export function AccountView() {
const { t } = useTranslation();
-
+ const dispatch = useDispatch();
const location = useLocation();
const queryParams = parse(location.search);
const showError = !!queryParams.error;
- const errorType = queryParams.error;
+ const errorType = queryParams.error as string;
const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED;
const history = useHistory();
@@ -72,7 +74,11 @@ function AccountView() {
history.push(location.pathname);
}}
>
-
+ dispatch(hideErrorModal())}
+ />
)}
@@ -111,5 +117,3 @@ function AccountView() {
);
}
-
-export default AccountView;
diff --git a/client/routes.jsx b/client/routes.jsx
index 4e399561ff..55d47453d6 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -15,7 +15,7 @@ import { SignupView } from './modules/User/pages/SignupView';
import { ResetPasswordView } from './modules/User/pages/ResetPasswordView';
import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
import { NewPasswordView } from './modules/User/pages/NewPasswordView';
-import AccountView from './modules/User/pages/AccountView';
+import { AccountView } from './modules/User/pages/AccountView';
import { CollectionView } from './modules/User/pages/CollectionView';
import DashboardView from './modules/User/pages/DashboardView';
import { getUser } from './modules/User/actions';
diff --git a/package-lock.json b/package-lock.json
index f07e6ee670..1e1c123183 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -174,6 +174,7 @@
"@types/react-dom": "^16.9.25",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-tabs": "^2.3.1",
"@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
@@ -16700,6 +16701,16 @@
"@types/react-router": "*"
}
},
+ "node_modules/@types/react-tabs": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+ "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
@@ -53423,6 +53434,15 @@
"@types/react-router": "*"
}
},
+ "@types/react-tabs": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+ "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
diff --git a/package.json b/package.json
index 1f531d396f..01d0ed4a7c 100644
--- a/package.json
+++ b/package.json
@@ -149,6 +149,7 @@
"@types/react-dom": "^16.9.25",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "@types/react-tabs": "^2.3.1",
"@types/react-transition-group": "^4.4.12",
"@types/sinon": "^17.0.4",
"@types/styled-components": "^5.1.34",
From 6dc69100da997ad004d233faf32a5801f5b339fb Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:45:01 +0000
Subject: [PATCH 53/61] client/modules/User/components/AccountForm: update to
ts, no-verify
---
.../modules/User/components/{AccountForm.jsx => AccountForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{AccountForm.jsx => AccountForm.tsx} (100%)
diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.tsx
similarity index 100%
rename from client/modules/User/components/AccountForm.jsx
rename to client/modules/User/components/AccountForm.tsx
From c53a8e4f05063c1d930b6a9cb2a48fde6a55286f Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:53:28 +0000
Subject: [PATCH 54/61] client/modules/User/components/AccountForm: add types &
update to named export
---
.../modules/User/components/AccountForm.tsx | 37 +++++++++++--------
.../User/components/AccountForm.unit.test.jsx | 2 +-
client/modules/User/pages/AccountView.tsx | 2 +-
3 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/client/modules/User/components/AccountForm.tsx b/client/modules/User/components/AccountForm.tsx
index 4ef40e4298..cb84078f9f 100644
--- a/client/modules/User/components/AccountForm.tsx
+++ b/client/modules/User/components/AccountForm.tsx
@@ -6,14 +6,21 @@ import { Button, ButtonTypes } from '../../../common/Button';
import { validateSettings } from '../../../utils/reduxFormUtils';
import { updateSettings, initiateVerification } from '../actions';
import { apiClient } from '../../../utils/apiClient';
+import {
+ DuplicateUserCheckQuery,
+ UpdateSettingsRequestBody
+} from '../../../../common/types';
+import { RootState } from '../../../reducers';
+import type { AccountForm as AccountFormType } from '../../../utils/reduxFormUtils';
-function asyncValidate(fieldToValidate, value) {
+function asyncValidate(fieldToValidate: 'username' | 'email', value: string) {
if (!value || value.trim().length === 0) {
return '';
}
- const queryParams = {};
+ const queryParams: DuplicateUserCheckQuery = {
+ check_type: fieldToValidate
+ };
queryParams[fieldToValidate] = value;
- queryParams.check_type = fieldToValidate;
return apiClient
.get('/signup/duplicate_check', { params: queryParams })
.then((response) => {
@@ -24,27 +31,27 @@ function asyncValidate(fieldToValidate, value) {
});
}
-function AccountForm() {
+export function AccountForm() {
const { t } = useTranslation();
- const user = useSelector((state) => state.user);
+ const user = useSelector((state: RootState) => state.user);
const dispatch = useDispatch();
- const handleInitiateVerification = (evt) => {
+ const handleInitiateVerification = (evt: React.MouseEvent) => {
evt.preventDefault();
dispatch(initiateVerification());
};
- function validateUsername(username) {
+ function validateUsername(username: AccountFormType['username']) {
if (username === user.username) return '';
return asyncValidate('username', username);
}
- function validateEmail(email) {
+ function validateEmail(email: AccountFormType['email']) {
if (email === user.email) return '';
return asyncValidate('email', email);
}
- function onSubmit(formProps) {
+ function onSubmit(formProps: UpdateSettingsRequestBody) {
return dispatch(updateSettings(formProps));
}
@@ -54,11 +61,13 @@ function AccountForm() {
validate={validateSettings}
onSubmit={onSubmit}
>
- {({ handleSubmit, submitting, invalid, restart }) => (
+ {({ handleSubmit, submitting, invalid, form }) => (
);
}
-
-export default AccountForm;
diff --git a/client/modules/User/components/AccountForm.unit.test.jsx b/client/modules/User/components/AccountForm.unit.test.jsx
index caaa7ddc39..b9d2baebe0 100644
--- a/client/modules/User/components/AccountForm.unit.test.jsx
+++ b/client/modules/User/components/AccountForm.unit.test.jsx
@@ -8,7 +8,7 @@ import {
act,
waitFor
} from '../../../test-utils';
-import AccountForm from './AccountForm';
+import { AccountForm } from './AccountForm';
import { initialTestState } from '../../../testData/testReduxStore';
import * as actions from '../actions';
diff --git a/client/modules/User/pages/AccountView.tsx b/client/modules/User/pages/AccountView.tsx
index 4e3fbc8ab2..03375d7466 100644
--- a/client/modules/User/pages/AccountView.tsx
+++ b/client/modules/User/pages/AccountView.tsx
@@ -5,7 +5,7 @@ import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { useHistory, useLocation } from 'react-router-dom';
import { parse } from 'query-string';
-import AccountForm from '../components/AccountForm';
+import { AccountForm } from '../components/AccountForm';
import {
SocialAuthButton,
SocialAuthServices
From e0d0342abccdd952c4afe5e506ce2dadda1fa67a Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:54:32 +0000
Subject: [PATCH 55/61] client/modules/User/components/APIKeyForm: update to
ts, no-verify
---
client/modules/User/components/{APIKeyForm.jsx => APIKeyForm.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{APIKeyForm.jsx => APIKeyForm.tsx} (100%)
diff --git a/client/modules/User/components/APIKeyForm.jsx b/client/modules/User/components/APIKeyForm.tsx
similarity index 100%
rename from client/modules/User/components/APIKeyForm.jsx
rename to client/modules/User/components/APIKeyForm.tsx
From 5dcca1fe880c2a87c843630242b83bb282c40265 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 01:59:32 +0000
Subject: [PATCH 56/61] client/modules/User/components/APIKeyForm: add types &
update to named export
---
client/common/Button.tsx | 1 +
client/modules/User/components/APIKeyForm.tsx | 14 +++++++-------
client/modules/User/pages/AccountView.tsx | 2 +-
3 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/client/common/Button.tsx b/client/common/Button.tsx
index d52abf3140..9b31c4acd1 100644
--- a/client/common/Button.tsx
+++ b/client/common/Button.tsx
@@ -92,6 +92,7 @@ export interface ButtonProps extends React.HTMLAttributes {
* but React will automatically convert a boolean prop to the correct string value.
*/
focusable?: boolean;
+ label?: string;
}
interface StyledButtonProps extends ButtonProps {
diff --git a/client/modules/User/components/APIKeyForm.tsx b/client/modules/User/components/APIKeyForm.tsx
index fda5945f87..c725f0f4f4 100644
--- a/client/modules/User/components/APIKeyForm.tsx
+++ b/client/modules/User/components/APIKeyForm.tsx
@@ -8,6 +8,8 @@ import CopyableInput from '../../IDE/components/CopyableInput';
import { createApiKey, removeApiKey } from '../actions';
import APIKeyList from './APIKeyList';
+import { RootState } from '../../../reducers';
+import { SanitisedApiKey } from '../../../../common/types';
export const APIKeyPropType = PropTypes.shape({
id: PropTypes.string.isRequired,
@@ -17,20 +19,20 @@ export const APIKeyPropType = PropTypes.shape({
lastUsedAt: PropTypes.string
});
-const APIKeyForm = () => {
+export const APIKeyForm = () => {
const { t } = useTranslation();
- const apiKeys = useSelector((state) => state.user.apiKeys);
+ const apiKeys = useSelector((state: RootState) => state.user.apiKeys) ?? [];
const dispatch = useDispatch();
const [keyLabel, setKeyLabel] = useState('');
- const addKey = (event) => {
+ const addKey = (event: React.FormEvent) => {
event.preventDefault();
dispatch(createApiKey(keyLabel));
setKeyLabel('');
};
- const removeKey = (key) => {
+ const removeKey = (key: SanitisedApiKey) => {
const message = t('APIKeyForm.ConfirmDelete', {
key_label: key.label
});
@@ -94,7 +96,7 @@ const APIKeyForm = () => {
)}
@@ -109,5 +111,3 @@ const APIKeyForm = () => {
);
};
-
-export default APIKeyForm;
diff --git a/client/modules/User/pages/AccountView.tsx b/client/modules/User/pages/AccountView.tsx
index 03375d7466..225e9e42e8 100644
--- a/client/modules/User/pages/AccountView.tsx
+++ b/client/modules/User/pages/AccountView.tsx
@@ -10,7 +10,7 @@ import {
SocialAuthButton,
SocialAuthServices
} from '../components/SocialAuthButton';
-import APIKeyForm from '../components/APIKeyForm';
+import { APIKeyForm } from '../components/APIKeyForm';
import Nav from '../../IDE/components/Header/Nav';
import ErrorModal from '../../IDE/components/ErrorModal';
import { hideErrorModal } from '../../IDE/actions/ide';
From 7a554338ae4a04bc5d5b3ae418b503f0882ab3ee Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 02:03:54 +0000
Subject: [PATCH 57/61] client/modules/User/components/APIKeyList: update to
ts,no-verify
---
client/modules/User/components/{APIKeyList.jsx => APIKeyList.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/modules/User/components/{APIKeyList.jsx => APIKeyList.tsx} (100%)
diff --git a/client/modules/User/components/APIKeyList.jsx b/client/modules/User/components/APIKeyList.tsx
similarity index 100%
rename from client/modules/User/components/APIKeyList.jsx
rename to client/modules/User/components/APIKeyList.tsx
From 24960271c69e939828d76d0036f8ab29b3216979 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 02:04:29 +0000
Subject: [PATCH 58/61] client/modules/User/components/APIKeyList: add types,
remove extra PropType & update to named export
---
client/modules/User/components/APIKeyForm.tsx | 12 +-----------
client/modules/User/components/APIKeyList.tsx | 18 ++++++------------
2 files changed, 7 insertions(+), 23 deletions(-)
diff --git a/client/modules/User/components/APIKeyForm.tsx b/client/modules/User/components/APIKeyForm.tsx
index c725f0f4f4..7a9abc8df8 100644
--- a/client/modules/User/components/APIKeyForm.tsx
+++ b/client/modules/User/components/APIKeyForm.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -6,19 +5,10 @@ import { Button, ButtonTypes } from '../../../common/Button';
import { PlusIcon } from '../../../common/icons';
import CopyableInput from '../../IDE/components/CopyableInput';
import { createApiKey, removeApiKey } from '../actions';
-
-import APIKeyList from './APIKeyList';
+import { APIKeyList } from './APIKeyList';
import { RootState } from '../../../reducers';
import { SanitisedApiKey } from '../../../../common/types';
-export const APIKeyPropType = PropTypes.shape({
- id: PropTypes.string.isRequired,
- token: PropTypes.string,
- label: PropTypes.string.isRequired,
- createdAt: PropTypes.string.isRequired,
- lastUsedAt: PropTypes.string
-});
-
export const APIKeyForm = () => {
const { t } = useTranslation();
const apiKeys = useSelector((state: RootState) => state.user.apiKeys) ?? [];
diff --git a/client/modules/User/components/APIKeyList.tsx b/client/modules/User/components/APIKeyList.tsx
index 17cd857354..a3692fad32 100644
--- a/client/modules/User/components/APIKeyList.tsx
+++ b/client/modules/User/components/APIKeyList.tsx
@@ -1,17 +1,18 @@
-import PropTypes from 'prop-types';
import React from 'react';
import { orderBy } from 'lodash';
import { useTranslation } from 'react-i18next';
-
-import { APIKeyPropType } from './APIKeyForm';
-
+import type { SanitisedApiKey } from '../../../../common/types';
import {
distanceInWordsToNow,
formatDateToString
} from '../../../utils/formatDate';
import TrashCanIcon from '../../../images/trash-can.svg';
-function APIKeyList({ apiKeys, onRemove }) {
+export interface APIKeyListProps {
+ apiKeys: SanitisedApiKey[];
+ onRemove: (key: SanitisedApiKey) => void;
+}
+export function APIKeyList({ apiKeys, onRemove }: APIKeyListProps) {
const { t } = useTranslation();
return (
@@ -50,10 +51,3 @@ function APIKeyList({ apiKeys, onRemove }) {
);
}
-
-APIKeyList.propTypes = {
- apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
- onRemove: PropTypes.func.isRequired
-};
-
-export default APIKeyList;
From 79d584d382a3750342cf1fcb9a5e3f6dc6780a47 Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 13:22:38 +0000
Subject: [PATCH 59/61] client/common/icons: update to ts, no-verify
---
client/common/{icons.jsx => icons.tsx} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename client/common/{icons.jsx => icons.tsx} (100%)
diff --git a/client/common/icons.jsx b/client/common/icons.tsx
similarity index 100%
rename from client/common/icons.jsx
rename to client/common/icons.tsx
From 48b37059cd7b29046e2e4e08a7fc27816cc0a6cb Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Sun, 26 Oct 2025 13:23:28 +0000
Subject: [PATCH 60/61] client/common/icons: add types & update snapshot test
---
client/common/icons.tsx | 40 ++++++++++++-------
.../__snapshots__/Nav.unit.test.jsx.snap | 8 ++--
2 files changed, 30 insertions(+), 18 deletions(-)
diff --git a/client/common/icons.tsx b/client/common/icons.tsx
index bd5a673e2a..80411bf937 100644
--- a/client/common/icons.tsx
+++ b/client/common/icons.tsx
@@ -26,13 +26,25 @@ import Filter from '../images/filter.svg';
import Cross from '../images/cross.svg';
import Copy from '../images/copy.svg';
+export interface IconColors {
+ default?: string;
+ hover?: string;
+}
+
+export interface IconProps extends React.SVGProps {
+ 'aria-label'?: string;
+ Icon?: IconColors;
+}
+
// HOC that adds the right web accessibility props
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
// could also give these a default size, color, etc. based on the theme
// Need to add size to these - like small icon, medium icon, large icon. etc.
-function withLabel(SvgComponent) {
- const StyledIcon = styled(SvgComponent)`
+function withLabel(
+ SvgComponent: React.ComponentType>
+) {
+ const StyledIcon = styled(SvgComponent)`
&&& {
color: ${(props) => props.Icon?.default};
& g,
@@ -53,27 +65,27 @@ function withLabel(SvgComponent) {
}
`;
- const Icon = (props) => {
- const { 'aria-label': ariaLabel } = props;
+ // Necessary because styled components inject a different type for the ref prop
+ type StyledIconProps = Omit<
+ React.ComponentProps,
+ 'ref'
+ > & {
+ ref?: React.Ref;
+ };
+
+ const Icon = (props: StyledIconProps) => {
+ const { 'aria-label': ariaLabel, ...rest } = props;
if (ariaLabel) {
return (
);
}
- return ;
- };
-
- Icon.propTypes = {
- 'aria-label': PropTypes.string
- };
-
- Icon.defaultProps = {
- 'aria-label': null
+ return ;
};
return Icon;
diff --git a/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap b/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
index 02e4776b1c..382e49aadd 100644
--- a/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
+++ b/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
@@ -351,7 +351,7 @@ exports[`Nav renders dashboard version for mobile 1`] = `
>
@@ -367,7 +367,7 @@ exports[`Nav renders dashboard version for mobile 1`] = `
>
@@ -971,7 +971,7 @@ exports[`Nav renders editor version for mobile 1`] = `
>
@@ -987,7 +987,7 @@ exports[`Nav renders editor version for mobile 1`] = `
>
From 12b1f7c8fe292a54bea9db6c64522be9fa24365f Mon Sep 17 00:00:00 2001
From: Claire Peng
Date: Mon, 27 Oct 2025 12:31:40 +0000
Subject: [PATCH 61/61] add GetRootState function type, and refactor
modules/User/actions & preferences
---
client/modules/IDE/actions/preferences.ts | 2 +-
client/modules/IDE/actions/preferences.types.ts | 4 +---
client/modules/User/actions.ts | 8 ++++----
client/reducers.ts | 3 +++
4 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/client/modules/IDE/actions/preferences.ts b/client/modules/IDE/actions/preferences.ts
index ccb5ef6a63..f626a20002 100644
--- a/client/modules/IDE/actions/preferences.ts
+++ b/client/modules/IDE/actions/preferences.ts
@@ -6,7 +6,6 @@ import type {
UpdatePreferencesDispatch,
SetPreferencesTabValue,
SetFontSizeValue,
- GetRootState,
SetLineNumbersValue,
SetAutocloseBracketsQuotesValue,
SetAutocompleteHinterValue,
@@ -20,6 +19,7 @@ import type {
SetLanguageValue,
SetThemeValue
} from './preferences.types';
+import type { GetRootState } from '../../../reducers';
function updatePreferences(
formParams: UpdatePreferencesRequestBody,
diff --git a/client/modules/IDE/actions/preferences.types.ts b/client/modules/IDE/actions/preferences.types.ts
index e54d28d3cd..dd56c65a77 100644
--- a/client/modules/IDE/actions/preferences.types.ts
+++ b/client/modules/IDE/actions/preferences.types.ts
@@ -1,6 +1,6 @@
import * as ActionTypes from '../../../constants';
import type { PreferencesState } from '../reducers/preferences';
-import type { RootState } from '../../../reducers';
+import type { GetRootState } from '../../../reducers';
// Value Definitions:
export type SetPreferencesTabValue = PreferencesState['tabIndex'];
@@ -112,5 +112,3 @@ export type PreferencesThunk = (
dispatch: UpdatePreferencesDispatch,
getState: GetRootState
) => void;
-
-export type GetRootState = () => RootState;
diff --git a/client/modules/User/actions.ts b/client/modules/User/actions.ts
index ad47bc4d29..b34c2bd1b4 100644
--- a/client/modules/User/actions.ts
+++ b/client/modules/User/actions.ts
@@ -23,7 +23,7 @@ import type {
UserPreferences,
VerifyEmailQuery
} from '../../../common/types';
-import { RootState } from '../../reducers';
+import type { GetRootState, RootState } from '../../reducers';
export function authError(error: Error) {
return {
@@ -76,7 +76,7 @@ export function validateAndLoginUser(formProps: {
}) {
return (
dispatch: ThunkDispatch,
- getState: () => RootState
+ getState: GetRootState
) => {
const state = getState();
const { previousPath } = state.ide;
@@ -115,7 +115,7 @@ export function validateAndLoginUser(formProps: {
* - Create a new user
*/
export function validateAndSignUpUser(formValues: CreateUserRequestBody) {
- return (dispatch: Dispatch, getState: () => RootState) => {
+ return (dispatch: Dispatch, getState: GetRootState) => {
const state = getState();
const { previousPath } = state.ide;
return new Promise((resolve) => {
@@ -161,7 +161,7 @@ export function getUser() {
}
export function validateSession() {
- return async (dispatch: Dispatch, getState: () => RootState) => {
+ return async (dispatch: Dispatch, getState: GetRootState) => {
try {
const response = await apiClient.get('/session');
const state = getState();
diff --git a/client/reducers.ts b/client/reducers.ts
index 47d9627f80..99f1b63b57 100644
--- a/client/reducers.ts
+++ b/client/reducers.ts
@@ -34,5 +34,8 @@ const rootReducer = combineReducers({
// Type for entire redux state
export type RootState = ReturnType;
+// Type for functions that get root state
+export type GetRootState = () => RootState;
+
// eslint-disable-next-line import/no-default-export
export default rootReducer;