From fa7d951d6ae0beeb1ad05673a186276d31288494 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sun, 26 Oct 2025 22:41:05 +0000 Subject: [PATCH 01/52] 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 02/52] 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 03/52] 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 04/52] 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 05/52] 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 06/52] 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 07/52] 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 08/52] 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 09/52] 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 10/52] 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 11/52] 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 12/52] 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 13/52] 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 14/52] 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 15/52] 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 16/52] 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 17/52] 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 18/52] 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 19/52] 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 20/52] 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 21/52] 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 22/52] 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 23/52] 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 24/52] 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 25/52] 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 26/52] 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 27/52] 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 28/52] 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 29/52] 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 30/52] 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 31/52] 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 32/52] 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 33/52] 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 34/52] 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 35/52] 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 36/52] 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 37/52] 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 38/52] 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 39/52] 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 40/52] 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 41/52] 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 }) => (
{ - handleSubmit(event).then(restart); + onSubmit={async (event: React.FormEvent) => { + const result = await handleSubmit(event); + form.restart(); + return result; }} > ) : (
)} @@ -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 44/52] 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 45/52] 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 46/52] 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 47/52] 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`] = ` >