From 6940a1081e49bfec4c3a0c1f0e4180831b873491 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 22:45:59 +0100 Subject: [PATCH 01/26] APIKeyForm & APIKeyList: migrate to ts, no-verify --- client/modules/User/components/{APIKeyForm.jsx => APIKeyForm.tsx} | 0 client/modules/User/components/{APIKeyList.jsx => APIKeyList.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename client/modules/User/components/{APIKeyForm.jsx => APIKeyForm.tsx} (100%) rename client/modules/User/components/{APIKeyList.jsx => APIKeyList.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 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 f670c2b3a6f187521241a164a7cf4e1572163e51 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 22:48:02 +0100 Subject: [PATCH 02/26] APIKeyForm & APIKeyList: add types, ammend SanitisedApiKey type to have token property, injectd by createToken --- client/modules/User/components/APIKeyForm.tsx | 26 +++++++------------ client/modules/User/components/APIKeyList.tsx | 20 ++++++-------- client/modules/User/pages/AccountView.jsx | 2 +- server/types/apiKey.ts | 6 +++-- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/client/modules/User/components/APIKeyForm.tsx b/client/modules/User/components/APIKeyForm.tsx index fda5945f87..3dc42ee9c8 100644 --- a/client/modules/User/components/APIKeyForm.tsx +++ b/client/modules/User/components/APIKeyForm.tsx @@ -7,30 +7,24 @@ 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 type { 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 -}); - -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 }); @@ -49,7 +43,7 @@ const APIKeyForm = () => { return

{t('APIKeyForm.NoTokens')}

; }; - const keyWithToken = apiKeys.find((k) => !!k.token); + const keyWithToken = apiKeys.find((k: SanitisedApiKey) => !!k.token); return (
@@ -77,7 +71,7 @@ const APIKeyForm = () => {
); }; - -export default APIKeyForm; diff --git a/client/modules/User/components/APIKeyList.tsx b/client/modules/User/components/APIKeyList.tsx index 17cd857354..cbc1eb12b2 100644 --- a/client/modules/User/components/APIKeyList.tsx +++ b/client/modules/User/components/APIKeyList.tsx @@ -1,17 +1,20 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { orderBy } from 'lodash'; import { useTranslation } from 'react-i18next'; - -import { APIKeyPropType } from './APIKeyForm'; - import { distanceInWordsToNow, formatDateToString } from '../../../utils/formatDate'; import TrashCanIcon from '../../../images/trash-can.svg'; +import type { SanitisedApiKey } from '../../../../common/types'; -function APIKeyList({ apiKeys, onRemove }) { +export function APIKeyList({ + apiKeys, + onRemove +}: { + apiKeys: SanitisedApiKey[]; + onRemove: (key: SanitisedApiKey) => void; +}) { const { t } = useTranslation(); return ( @@ -50,10 +53,3 @@ function APIKeyList({ apiKeys, onRemove }) {
); } - -APIKeyList.propTypes = { - apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired, - onRemove: PropTypes.func.isRequired -}; - -export default APIKeyList; diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index e9b3da7c9a..dfbfd27cbe 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -7,7 +7,7 @@ import { useHistory, useLocation } from 'react-router-dom'; import { parse } from 'query-string'; import AccountForm from '../components/AccountForm'; import SocialAuthButton 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 Overlay from '../../App/components/Overlay'; diff --git a/server/types/apiKey.ts b/server/types/apiKey.ts index 21ba15aa04..a8703ff55a 100644 --- a/server/types/apiKey.ts +++ b/server/types/apiKey.ts @@ -23,7 +23,9 @@ export interface ApiKeyDocument * and can be exposed to the client */ export interface SanitisedApiKey - extends Pick {} + extends Pick { + token?: string; // sometimes injected by userController.createApiKey +} /** Mongoose model for API Key */ export interface ApiKeyModel extends Model {} @@ -37,7 +39,7 @@ export type ApiKeyResponseOrError = ApiKeyResponse | Error; /** Response for api-key related endpoints, containing list of keys */ export interface ApiKeyResponse { - apiKeys: ApiKeyDocument[]; + apiKeys: SanitisedApiKey[]; } /** userController.createApiKey - Request */ From c1f8e1208139cc78cf10e68954c68f570de280d4 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:02:02 +0100 Subject: [PATCH 03/26] 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 3b40eaf000676a75a42c8e631c89e49a5f962e75 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:03:38 +0100 Subject: [PATCH 04/26] NewPasswordForm: update with types and to default export --- .../User/components/NewPasswordForm.tsx | 18 ++++++++---------- .../components/NewPasswordForm.unit.test.jsx | 2 +- client/modules/User/pages/NewPasswordView.jsx | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/client/modules/User/components/NewPasswordForm.tsx b/client/modules/User/components/NewPasswordForm.tsx index feca326c77..80d4054484 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,18 @@ import { useTranslation } from 'react-i18next'; import { validateNewPassword } from '../../../utils/reduxFormUtils'; import { updatePassword } from '../actions'; import { Button, ButtonTypes } from '../../../common/Button'; +import type { ResetOrUpdatePasswordRequestParams } from '../../../../common/types'; +import type { NewPasswordForm as NewPasswordFormType } from '../../../utils/reduxFormUtils'; -function NewPasswordForm(props) { - const { resetPasswordToken } = props; +export type NewPasswordFormProps = { + resetPasswordToken: ResetOrUpdatePasswordRequestParams['token']; +}; + +export function NewPasswordForm({ resetPasswordToken }: NewPasswordFormProps) { const { t } = useTranslation(); const dispatch = useDispatch(); - function onSubmit(formProps) { + function onSubmit(formProps: NewPasswordFormType) { return dispatch(updatePassword(formProps, resetPasswordToken)); } @@ -75,9 +79,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.jsx b/client/modules/User/pages/NewPasswordView.jsx index 2bc310e6a3..2817cded9f 100644 --- a/client/modules/User/pages/NewPasswordView.jsx +++ b/client/modules/User/pages/NewPasswordView.jsx @@ -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 174ba64433fbd96a728428f9ef4c7e850a4ec00d Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:06:12 +0100 Subject: [PATCH 05/26] client/modules/Legal/components/PolicyContainer: update to ts, no-verify --- .../Legal/components/{PolicyContainer.jsx => PolicyContainer.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/Legal/components/{PolicyContainer.jsx => PolicyContainer.tsx} (100%) diff --git a/client/modules/Legal/components/PolicyContainer.jsx b/client/modules/Legal/components/PolicyContainer.tsx similarity index 100% rename from client/modules/Legal/components/PolicyContainer.jsx rename to client/modules/Legal/components/PolicyContainer.tsx From 73d735ecd922334df9ece867d3ba2427a2b11638 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:07:02 +0100 Subject: [PATCH 06/26] client/modules/Legal/components/PolicyContainer: add types and update to named export --- client/modules/Legal/components/PolicyContainer.tsx | 13 +++++-------- client/modules/Legal/pages/Legal.jsx | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/client/modules/Legal/components/PolicyContainer.tsx b/client/modules/Legal/components/PolicyContainer.tsx index fdc311d435..21e0295e3f 100644 --- a/client/modules/Legal/components/PolicyContainer.tsx +++ b/client/modules/Legal/components/PolicyContainer.tsx @@ -2,7 +2,6 @@ import React from 'react'; import styled from 'styled-components'; import ReactMarkdown from 'react-markdown'; import remarkSlug from 'remark-slug'; -import PropTypes from 'prop-types'; import { remSize, prop } from '../../../theme'; const PolicyContainerMain = styled.main` @@ -48,16 +47,14 @@ const PolicyContainerMain = styled.main` } `; -function PolicyContainer({ policy }) { +export interface PolicyContainerProps { + policy: string; +} + +export function PolicyContainer({ policy }: PolicyContainerProps) { return ( {policy} ); } - -PolicyContainer.propTypes = { - policy: PropTypes.string.isRequired -}; - -export default PolicyContainer; diff --git a/client/modules/Legal/pages/Legal.jsx b/client/modules/Legal/pages/Legal.jsx index 4ba5cb5bbc..e93255d62d 100644 --- a/client/modules/Legal/pages/Legal.jsx +++ b/client/modules/Legal/pages/Legal.jsx @@ -9,7 +9,7 @@ import { RootPage } from '../../../components/RootPage'; import { remSize } from '../../../theme'; import Loader from '../../App/components/loader'; import Nav from '../../IDE/components/Header/Nav'; -import PolicyContainer from '../components/PolicyContainer'; +import { PolicyContainer } from '../components/PolicyContainer'; const StyledTabList = styled.nav` display: flex; From 53d2693b662871b4ac51c649c3fb0ecdbd2d1df7 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:08:56 +0100 Subject: [PATCH 07/26] clean up --- client/modules/User/components/APIKeyList.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/modules/User/components/APIKeyList.tsx b/client/modules/User/components/APIKeyList.tsx index cbc1eb12b2..a3609833ae 100644 --- a/client/modules/User/components/APIKeyList.tsx +++ b/client/modules/User/components/APIKeyList.tsx @@ -8,13 +8,12 @@ import { import TrashCanIcon from '../../../images/trash-can.svg'; import type { SanitisedApiKey } from '../../../../common/types'; -export function APIKeyList({ - apiKeys, - onRemove -}: { +export interface APIKeyListProps { apiKeys: SanitisedApiKey[]; onRemove: (key: SanitisedApiKey) => void; -}) { +} + +export function APIKeyList({ apiKeys, onRemove }: APIKeyListProps) { const { t } = useTranslation(); return ( From fded6732b65ace4aa3c3bd1d79793e5d38d206ae Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:11:48 +0100 Subject: [PATCH 08/26] client/modules/Legal/pages/TermsOfUse: update to ts, no-verify --- client/modules/Legal/pages/{TermsOfUse.jsx => TermsOfUse.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/Legal/pages/{TermsOfUse.jsx => TermsOfUse.tsx} (100%) diff --git a/client/modules/Legal/pages/TermsOfUse.jsx b/client/modules/Legal/pages/TermsOfUse.tsx similarity index 100% rename from client/modules/Legal/pages/TermsOfUse.jsx rename to client/modules/Legal/pages/TermsOfUse.tsx From 9a7be10412527d6fd9df16da185ece3506a41a5f Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:12:27 +0100 Subject: [PATCH 09/26] client/modules/Legal/pages/TermsOfUse: update to named export --- client/modules/Legal/pages/TermsOfUse.tsx | 4 +--- client/routes.jsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/modules/Legal/pages/TermsOfUse.tsx b/client/modules/Legal/pages/TermsOfUse.tsx index 6b3b553942..b0dca61481 100644 --- a/client/modules/Legal/pages/TermsOfUse.tsx +++ b/client/modules/Legal/pages/TermsOfUse.tsx @@ -2,10 +2,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import Legal from './Legal'; -function TermsOfUse() { +export function TermsOfUse() { const { t } = useTranslation(); return ; } - -export default TermsOfUse; diff --git a/client/routes.jsx b/client/routes.jsx index 8926a95bdd..40440e85b0 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -9,7 +9,7 @@ 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 { 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'; From 3b9a4f4dcefe9cc5d428aab708867c3404496214 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:13:24 +0100 Subject: [PATCH 10/26] client/modules/Legal/pages/PrivacyPolicy: update to ts, no-verify --- .../modules/Legal/pages/{PrivacyPolicy.jsx => PrivacyPolicy.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/Legal/pages/{PrivacyPolicy.jsx => PrivacyPolicy.tsx} (100%) diff --git a/client/modules/Legal/pages/PrivacyPolicy.jsx b/client/modules/Legal/pages/PrivacyPolicy.tsx similarity index 100% rename from client/modules/Legal/pages/PrivacyPolicy.jsx rename to client/modules/Legal/pages/PrivacyPolicy.tsx From ace6854f17f70da39e4292ddca487508ee0df313 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:14:19 +0100 Subject: [PATCH 11/26] client/modules/Legal/pages/PrivacyPolicy: update to named export --- client/modules/Legal/pages/PrivacyPolicy.tsx | 4 +--- client/routes.jsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/modules/Legal/pages/PrivacyPolicy.tsx b/client/modules/Legal/pages/PrivacyPolicy.tsx index ef39ed876c..c00f1ae6fd 100644 --- a/client/modules/Legal/pages/PrivacyPolicy.tsx +++ b/client/modules/Legal/pages/PrivacyPolicy.tsx @@ -2,12 +2,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import Legal from './Legal'; -function PrivacyPolicy() { +export function PrivacyPolicy() { const { t } = useTranslation(); return ( ); } - -export default PrivacyPolicy; diff --git a/client/routes.jsx b/client/routes.jsx index 40440e85b0..31266e70f2 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -8,7 +8,7 @@ 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 { 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'; From 46352802e45631908dd6720eb6fb7e2067f2f30f Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:15:13 +0100 Subject: [PATCH 12/26] client/modules/Legal/pages/CodeOfConduct: update to ts, no verify --- .../modules/Legal/pages/{CodeOfConduct.jsx => CodeOfConduct.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/Legal/pages/{CodeOfConduct.jsx => CodeOfConduct.tsx} (100%) diff --git a/client/modules/Legal/pages/CodeOfConduct.jsx b/client/modules/Legal/pages/CodeOfConduct.tsx similarity index 100% rename from client/modules/Legal/pages/CodeOfConduct.jsx rename to client/modules/Legal/pages/CodeOfConduct.tsx From c650e38cb4aa3f7dce10f82161d220f96d1e2233 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:16:36 +0100 Subject: [PATCH 13/26] client/modules/Legal/pages/CodeOfConduct: update to named export --- client/modules/Legal/pages/CodeOfConduct.tsx | 4 +--- client/routes.jsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/modules/Legal/pages/CodeOfConduct.tsx b/client/modules/Legal/pages/CodeOfConduct.tsx index c961ec74b6..7ab58ff148 100644 --- a/client/modules/Legal/pages/CodeOfConduct.tsx +++ b/client/modules/Legal/pages/CodeOfConduct.tsx @@ -2,12 +2,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import Legal from './Legal'; -function CodeOfConduct() { +export function CodeOfConduct() { const { t } = useTranslation(); return ( ); } - -export default CodeOfConduct; diff --git a/client/routes.jsx b/client/routes.jsx index 31266e70f2..99e900c43e 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -7,7 +7,7 @@ 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 { 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'; From 73858230a0bd55107e06ee03f29c3d6463d6f15c Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:21:54 +0100 Subject: [PATCH 14/26] client/modules/Legal/pages/Legal: update to ts, no-verify --- client/modules/Legal/pages/{Legal.jsx => Legal.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/Legal/pages/{Legal.jsx => Legal.tsx} (100%) diff --git a/client/modules/Legal/pages/Legal.jsx b/client/modules/Legal/pages/Legal.tsx similarity index 100% rename from client/modules/Legal/pages/Legal.jsx rename to client/modules/Legal/pages/Legal.tsx From 813f61482513ab5add4a1d101d371774a63d2d5b Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 25 Oct 2025 23:22:21 +0100 Subject: [PATCH 15/26] client/modules/Legal/pages/Legal: add types, update to default export, install helmet types --- client/modules/Legal/pages/CodeOfConduct.tsx | 2 +- client/modules/Legal/pages/Legal.tsx | 29 +++++++++----------- client/modules/Legal/pages/PrivacyPolicy.tsx | 2 +- client/modules/Legal/pages/TermsOfUse.tsx | 2 +- package-lock.json | 20 ++++++++++++++ package.json | 2 +- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/client/modules/Legal/pages/CodeOfConduct.tsx b/client/modules/Legal/pages/CodeOfConduct.tsx index 7ab58ff148..d05e4bb78a 100644 --- a/client/modules/Legal/pages/CodeOfConduct.tsx +++ b/client/modules/Legal/pages/CodeOfConduct.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import Legal from './Legal'; +import { Legal } from './Legal'; export function CodeOfConduct() { const { t } = useTranslation(); diff --git a/client/modules/Legal/pages/Legal.tsx b/client/modules/Legal/pages/Legal.tsx index e93255d62d..6690b08ed7 100644 --- a/client/modules/Legal/pages/Legal.tsx +++ b/client/modules/Legal/pages/Legal.tsx @@ -1,5 +1,4 @@ import axios from 'axios'; -import PropTypes from 'prop-types'; import React, { useEffect, useState } from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; @@ -22,7 +21,19 @@ const StyledTabList = styled.nav` } `; -function Legal({ policyFile, title }) { +export interface LegalProps { + /** + * Used in the HTML tag. + * TODO: pass this to the Nav to use as the mobile title. + */ + title: string; + /** + * Path of the markdown '.md' file, relative to the /public directory. + */ + policyFile: string; +} + +export function Legal({ policyFile, title }: LegalProps) { const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(true); const [policy, setPolicy] = useState(''); @@ -55,17 +66,3 @@ function Legal({ policyFile, title }) { </RootPage> ); } - -Legal.propTypes = { - /** - * Used in the HTML <title> tag. - * TODO: pass this to the Nav to use as the mobile title. - */ - title: PropTypes.string.isRequired, - /** - * Path of the markdown '.md' file, relative to the /public directory. - */ - policyFile: PropTypes.string.isRequired -}; - -export default Legal; diff --git a/client/modules/Legal/pages/PrivacyPolicy.tsx b/client/modules/Legal/pages/PrivacyPolicy.tsx index c00f1ae6fd..178736709c 100644 --- a/client/modules/Legal/pages/PrivacyPolicy.tsx +++ b/client/modules/Legal/pages/PrivacyPolicy.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import Legal from './Legal'; +import { Legal } from './Legal'; export function PrivacyPolicy() { const { t } = useTranslation(); diff --git a/client/modules/Legal/pages/TermsOfUse.tsx b/client/modules/Legal/pages/TermsOfUse.tsx index b0dca61481..45bbfd93a3 100644 --- a/client/modules/Legal/pages/TermsOfUse.tsx +++ b/client/modules/Legal/pages/TermsOfUse.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import Legal from './Legal'; +import { Legal } from './Legal'; export function TermsOfUse() { const { t } = useTranslation(); 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 438725d8bdd0f5d27bd555193a9786f8e95b58d0 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sat, 25 Oct 2025 23:31:31 +0100 Subject: [PATCH 16/26] client/modules/About: update files to ts, no-verify --- client/modules/About/{About.styles.js => About.styles.ts} | 0 client/modules/About/pages/{About.jsx => About.tsx} | 0 client/modules/About/statics/{aboutData.js => aboutData.ts} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename client/modules/About/{About.styles.js => About.styles.ts} (100%) rename client/modules/About/pages/{About.jsx => About.tsx} (100%) rename client/modules/About/statics/{aboutData.js => aboutData.ts} (100%) diff --git a/client/modules/About/About.styles.js b/client/modules/About/About.styles.ts similarity index 100% rename from client/modules/About/About.styles.js rename to client/modules/About/About.styles.ts diff --git a/client/modules/About/pages/About.jsx b/client/modules/About/pages/About.tsx similarity index 100% rename from client/modules/About/pages/About.jsx rename to client/modules/About/pages/About.tsx diff --git a/client/modules/About/statics/aboutData.js b/client/modules/About/statics/aboutData.ts similarity index 100% rename from client/modules/About/statics/aboutData.js rename to client/modules/About/statics/aboutData.ts From c07f92f9ce098a8e467077f08e97b351637dd284 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sat, 25 Oct 2025 23:32:15 +0100 Subject: [PATCH 17/26] client/modules/About: add types and update to named exports --- client/modules/About/pages/About.tsx | 42 ++++++++++------------- client/modules/About/statics/aboutData.ts | 20 +++++++++-- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/client/modules/About/pages/About.tsx b/client/modules/About/pages/About.tsx index 840278cbb7..e6730cf96f 100644 --- a/client/modules/About/pages/About.tsx +++ b/client/modules/About/pages/About.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { Helmet } from 'react-helmet'; -import { useTranslation } from 'react-i18next'; +import { TFunction, useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { @@ -27,8 +26,15 @@ import packageData from '../../../../package.json'; import HeartIcon from '../../../images/heart.svg'; import AsteriskIcon from '../../../images/p5-asterisk.svg'; import LogoIcon from '../../../images/p5js-square-logo.svg'; +import { RootState } from '../../../reducers'; +import { AboutSectionInfoSection } from '../statics/aboutData'; -const AboutSection = ({ section, t }) => ( +interface AboutSectionProps { + section: AboutSectionInfoSection; + t: TFunction<'translation'>; +} + +const AboutSection = ({ section, t }: AboutSectionProps) => ( <Section> <h2>{t(section.header)}</h2> <SectionContainer> @@ -47,11 +53,13 @@ const AboutSection = ({ section, t }) => ( </Section> ); -const About = () => { +export const About = () => { const { t } = useTranslation(); - const p5version = useSelector((state) => { - const index = state.files.find((file) => file.name === 'index.html'); + const p5version = useSelector((state: RootState) => { + const index = state.files.find( + (file: { name: string }) => file.name === 'index.html' + ); return index?.content.match(/\/p5@([\d.]+)\//)?.[1]; }); @@ -91,7 +99,11 @@ const About = () => { </Intro> {AboutSectionInfo.map((section) => ( - <AboutSection key={t(section.header)} section={section} t={t} /> + <AboutSection + key={t(section.header) as string} + section={section} + t={t} + /> ))} <Contact> @@ -152,19 +164,3 @@ const About = () => { </RootPage> ); }; - -AboutSection.propTypes = { - section: PropTypes.shape({ - header: PropTypes.string.isRequired, - items: PropTypes.arrayOf( - PropTypes.shape({ - url: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string.isRequired - }) - ).isRequired - }).isRequired, - t: PropTypes.func.isRequired -}; - -export default About; diff --git a/client/modules/About/statics/aboutData.ts b/client/modules/About/statics/aboutData.ts index a8623cfa7f..8d884acb9e 100644 --- a/client/modules/About/statics/aboutData.ts +++ b/client/modules/About/statics/aboutData.ts @@ -1,4 +1,9 @@ -export const ContactSectionLinks = [ +export interface ContactSectionLink { + label: string; + href: string; +} + +export const ContactSectionLinks: ContactSectionLink[] = [ { label: 'About.Github', href: 'https://github.com/processing/p5.js-web-editor' @@ -22,7 +27,18 @@ export const ContactSectionLinks = [ } ]; -export const AboutSectionInfo = [ +export interface AboutSectionInfoItem { + url: string; + title: string; + description: string; +} + +export interface AboutSectionInfoSection { + header: string; + items: AboutSectionInfoItem[]; +} + +export const AboutSectionInfo: AboutSectionInfoSection[] = [ { header: 'About.NewP5', items: [ From 5453102ac380c019da6742817f4d3fb09e9d2e06 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sat, 25 Oct 2025 23:50:49 +0100 Subject: [PATCH 18/26] client/modules/User/components/LoginForm: update to ts, no-verify --- 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 443eb543ff1bff7e3ee7ef7104747f53b8be4048 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sat, 25 Oct 2025 23:51:23 +0100 Subject: [PATCH 19/26] useSyncFormTranslations: update to check for null formRef --- client/common/useSyncFormTranslations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 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<FormLike>, + formRef: MutableRefObject<FormLike | null>, language: string ) => { useEffect(() => { - const form = formRef.current; + const form = formRef?.current; if (!form) return; const { values } = form.getState(); From 25878597cd9b8d7a434dc5c8f8f3bc79fa26fdf3 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sat, 25 Oct 2025 23:52:17 +0100 Subject: [PATCH 20/26] client/modules/User/components/LoginForm: add types and update to named export --- client/modules/User/components/LoginForm.tsx | 10 +++++----- client/modules/User/components/LoginForm.unit.test.jsx | 2 +- client/modules/User/pages/LoginView.jsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/modules/User/components/LoginForm.tsx b/client/modules/User/components/LoginForm.tsx index 5bac902ecf..182ef80f07 100644 --- a/client/modules/User/components/LoginForm.tsx +++ b/client/modules/User/components/LoginForm.tsx @@ -7,16 +7,18 @@ import { Button, ButtonTypes } from '../../../common/Button'; import { validateLogin } from '../../../utils/reduxFormUtils'; import { validateAndLoginUser } from '../actions'; import { useSyncFormTranslations } from '../../../common/useSyncFormTranslations'; +import type { LoginForm as LoginFormType } from '../../../utils/reduxFormUtils'; +import type { FormLike } from '../../../common/useSyncFormTranslations'; -function LoginForm() { +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: React.MutableRefObject<FormLike | null> = useRef(null); const handleVisibility = () => { setShowPassword(!showPassword); @@ -114,5 +116,3 @@ function LoginForm() { </Form> ); } - -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.jsx b/client/modules/User/pages/LoginView.jsx index b931c50ea8..4fcd75a834 100644 --- a/client/modules/User/pages/LoginView.jsx +++ b/client/modules/User/pages/LoginView.jsx @@ -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 from '../components/SocialAuthButton'; import Nav from '../../IDE/components/Header/Nav'; import { RootPage } from '../../../components/RootPage'; From 48fab82e4c5cf84a7bfca95cbde4243a15f654c9 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 00:09:05 +0100 Subject: [PATCH 21/26] 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 a571a110d008752865cc9e1c09e3ec24d65134ab Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 00:09:46 +0100 Subject: [PATCH 22/26] AccountForm: update with types and to named export --- .../modules/User/components/AccountForm.tsx | 32 ++++++++++--------- .../User/components/AccountForm.unit.test.jsx | 2 +- client/modules/User/pages/AccountView.jsx | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/client/modules/User/components/AccountForm.tsx b/client/modules/User/components/AccountForm.tsx index 4ef40e4298..718f111de9 100644 --- a/client/modules/User/components/AccountForm.tsx +++ b/client/modules/User/components/AccountForm.tsx @@ -6,14 +6,16 @@ import { Button, ButtonTypes } from '../../../common/Button'; import { validateSettings } from '../../../utils/reduxFormUtils'; import { updateSettings, initiateVerification } from '../actions'; import { apiClient } from '../../../utils/apiClient'; +import { RootState } from '../../../reducers'; +import type { AccountForm as AccountFormType } from '../../../utils/reduxFormUtils'; +import type { DuplicateUserCheckQuery } from '../../../../common/types'; -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 +26,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: AccountFormType) { return dispatch(updateSettings(formProps)); } @@ -54,11 +56,13 @@ function AccountForm() { validate={validateSettings} onSubmit={onSubmit} > - {({ handleSubmit, submitting, invalid, restart }) => ( + {({ handleSubmit, submitting, invalid, form }) => ( <form className="form" - onSubmit={(event) => { - handleSubmit(event).then(restart); + onSubmit={async (event: React.FormEvent) => { + const result = await handleSubmit(event); + form.restart(); + return result; }} > <Field @@ -98,7 +102,7 @@ function AccountForm() { </span> ) : ( <Button - onClick={handleInitiateVerification} + onClick={() => handleInitiateVerification} className="form__resend-button" > {t('AccountForm.Resend')} @@ -183,5 +187,3 @@ function AccountForm() { </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.jsx b/client/modules/User/pages/AccountView.jsx index dfbfd27cbe..7a151f1ec8 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -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 from '../components/SocialAuthButton'; import { APIKeyForm } from '../components/APIKeyForm'; import Nav from '../../IDE/components/Header/Nav'; From 70d996b8df50a44967e17171b66994967242dd2f Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 00:24:57 +0100 Subject: [PATCH 23/26] 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 49b2f6b21ac2ae548640b663286c9d1d51260c96 Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 00:28:46 +0100 Subject: [PATCH 24/26] client/modules/User/components/DashboardTabSwitcher: add types and update to named export --- .../User/components/DashboardTabSwitcher.tsx | 21 ++++++++++--------- client/modules/User/pages/DashboardView.jsx | 5 +++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/client/modules/User/components/DashboardTabSwitcher.tsx b/client/modules/User/components/DashboardTabSwitcher.tsx index 7afbe5aec8..d960a31678 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'; @@ -25,7 +24,17 @@ const FilterOptions = styled(Options)` } `; -const DashboardTabSwitcher = ({ currentTab, isOwner, username }) => { +export interface DashboardTabSwitcherProps { + currentTab: string; + isOwner: boolean; + 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 }) => { </div> ); }; - -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 = () => { <div className="dashboard-header__header"> <h2 className="dashboard-header__header__title">{ownerName()}</h2> <div className="dashboard-header__nav"> - <DashboardTabSwitcherPublic + <DashboardTabSwitcher currentTab={currentTab} isOwner={isOwner()} username={params.username} From 9a7a6b6b952dcd1b976dc644198fd8ed9f2f46ec Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 00:30:06 +0100 Subject: [PATCH 25/26] temporary update to common/icons --- client/common/icons.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/common/icons.jsx b/client/common/icons.jsx index bd5a673e2a..784d40040d 100644 --- a/client/common/icons.jsx +++ b/client/common/icons.jsx @@ -73,7 +73,7 @@ function withLabel(SvgComponent) { }; Icon.defaultProps = { - 'aria-label': null + 'aria-label': undefined }; return Icon; From d21b4e854477a4938c7f153a78c27f5e16bc89cf Mon Sep 17 00:00:00 2001 From: Claire Peng <clairepeng94@gmail.com> Date: Sun, 26 Oct 2025 13:03:24 +0000 Subject: [PATCH 26/26] WIP migrate user reducers/actions, wip --- .../modules/User/{actions.js => actions.ts} | 150 +++++++++++++----- .../modules/User/{reducers.js => reducers.ts} | 0 2 files changed, 108 insertions(+), 42 deletions(-) rename client/modules/User/{actions.js => actions.ts} (68%) rename client/modules/User/{reducers.js => reducers.ts} (100%) diff --git a/client/modules/User/actions.js b/client/modules/User/actions.ts similarity index 68% rename from client/modules/User/actions.js rename to client/modules/User/actions.ts index 20e6729b0d..be98cf1fa6 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.ts @@ -1,41 +1,61 @@ import { FORM_ERROR } from 'final-form'; +import { AnyAction, Dispatch } from 'redux'; 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 { + CookieConsentOptions, + CreateUserRequestBody, + Error, + PublicUser, + ResetOrUpdatePasswordRequestParams, + ResetPasswordInitiateRequestBody, + SanitisedApiKey, + UpdatePasswordRequestBody, + UpdateSettingsRequestBody, + UserPreferences +} from '../../../common/types'; +import { RootState } from '../../reducers'; +import type { GetRootState } from '../IDE/actions/preferences.types'; -export function authError(error) { +export function authError(error: Error) { return { type: ActionTypes.AUTH_ERROR, payload: error }; } -export function signUpUser(formValues) { +export function signUpUser(formValues: CreateUserRequestBody) { return apiClient.post('/signup', formValues); } -export function loginUser(formValues) { +export function loginUser(formValues: /** TODO: replace with actual type when server/session.controller is migrated */ { + username: string; + password: string; +}) { return apiClient.post('/login', formValues); } -export function authenticateUser(user) { +export function authenticateUser( + user: PublicUser /** Not sure if UserDocument or PublicUser, to check after relevant route is migrated */ +) { 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 @@ -43,12 +63,12 @@ export function setPreferences(preferences) { } export function validateAndLoginUser(formProps) { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: GetRootState) => { const state = getState(); const { previousPath } = state.ide; - return new Promise((resolve) => { + return new Promise<void | Error>((resolve) => { loginUser(formProps) - .then((response) => { + .then((response: { data: PublicUser }) => { dispatch(authenticateUser(response.data)); dispatch(setPreferences(response.data.preferences)); dispatch( @@ -70,10 +90,10 @@ export function validateAndLoginUser(formProps) { } export function validateAndSignUpUser(formValues) { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: GetRootState) => { const state = getState(); const { previousPath } = state.ide; - return new Promise((resolve) => { + return new Promise<void | Error>((resolve) => { signUpUser(formValues) .then((response) => { dispatch(authenticateUser(response.data)); @@ -91,7 +111,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 +126,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 +136,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 +144,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 +152,7 @@ export function validateSession() { }; } -export function resetProject(dispatch) { +export function resetProject(dispatch: Dispatch) { dispatch({ type: ActionTypes.RESET_PROJECT }); @@ -143,7 +163,7 @@ export function resetProject(dispatch) { } export function logoutUser() { - return (dispatch) => { + return (dispatch: Dispatch) => { apiClient .get('/logout') .then(() => { @@ -159,9 +179,11 @@ export function logoutUser() { }; } -export function initiateResetPassword(formValues) { - return (dispatch) => - new Promise((resolve) => { +export function initiateResetPassword( + formValues: ResetPasswordInitiateRequestBody +) { + return (dispatch: Dispatch) => + new Promise<void | Error>((resolve) => { dispatch({ type: ActionTypes.RESET_PASSWORD_INITIATE }); @@ -180,7 +202,7 @@ export function initiateResetPassword(formValues) { } export function initiateVerification() { - return (dispatch) => { + return (dispatch: Dispatch) => { dispatch({ type: ActionTypes.EMAIL_VERIFICATION_INITIATE }); @@ -199,8 +221,10 @@ export function initiateVerification() { }; } -export function verifyEmailConfirmation(token) { - return (dispatch) => { +export function verifyEmailConfirmation( + token: ResetOrUpdatePasswordRequestParams['token'] +) { + return (dispatch: Dispatch) => { dispatch({ type: ActionTypes.EMAIL_VERIFICATION_VERIFY, state: 'checking' @@ -229,8 +253,10 @@ export function resetPasswordReset() { }; } -export function validateResetPasswordToken(token) { - return (dispatch) => { +export function validateResetPasswordToken( + token: ResetOrUpdatePasswordRequestParams['token'] +) { + return (dispatch: Dispatch) => { apiClient .get(`/reset-password/${token}`) .then(() => { @@ -244,9 +270,12 @@ export function validateResetPasswordToken(token) { }; } -export function updatePassword(formValues, token) { - return (dispatch) => - new Promise((resolve) => +export function updatePassword( + formValues: UpdatePasswordRequestBody, + token: ResetOrUpdatePasswordRequestParams['token'] +) { + return (dispatch: Dispatch) => + new Promise<void | Error>((resolve) => apiClient .post(`/reset-password/${token}`, formValues) .then((response) => { @@ -263,20 +292,30 @@ 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) => { +export function updateSettings(formValues: UpdateSettingsRequestBody) { + return (dispatch: Dispatch) => + new Promise<void | Error>((resolve) => { if (!formValues.currentPassword && formValues.newPassword) { dispatch(showToast(5500)); dispatch(setToastText('Toast.EmptyCurrentPass')); @@ -313,15 +352,24 @@ 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) => +/** + * - Method: `POST` + * - Endpoint: `/account/api-keys` + * - Authenticated: `true` + * - Id: `UserController.createApiKey` + * + * Description: + * - Create API key + */ +export function createApiKey(label: SanitisedApiKey['label']) { + return (dispatch: Dispatch) => apiClient .post('/account/api-keys', { label }) .then((response) => { @@ -333,8 +381,17 @@ export function createApiKey(label) { }); } -export function removeApiKey(keyId) { - return (dispatch) => +/** + * - Method: `DELETE` + * - Endpoint: `/account/api-keys/:keyId` + * - Authenticated: `true` + * - Id: `UserController.removeApiKey` + * + * Description: + * - Remove API key + */ +export function removeApiKey(keyId: SanitisedApiKey['id']) { + return (dispatch: Dispatch) => apiClient .delete(`/account/api-keys/${keyId}`) .then((response) => { @@ -349,8 +406,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 +422,18 @@ export function unlinkService(service) { }; } -export function setUserCookieConsent(cookieConsent) { +/** + * - Method: `PUT` + * - Endpoint: `/cookie-consent` + * - Authenticated: `true` + * - Id: `UserController.updatePreferences` + * + * Description: + * - Update user cookie consent + */ +export function setUserCookieConsent(cookieConsent: CookieConsentOptions) { // maybe also send this to the server rn? - return (dispatch) => { + return (dispatch: Dispatch) => { apiClient .put('/cookie-consent', { cookieConsent }) .then(() => { 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