From 28e9e04669e030f15176cfcbc282dc6ba3ff3dfe Mon Sep 17 00:00:00 2001 From: Matt Davidson Date: Thu, 7 May 2026 14:45:52 -0700 Subject: [PATCH] feat: convert components and pages to TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 12 files migrated client-side, completing the JS → TS transition: - 6 components: Alert, Home, Layout, LoginWithEmail, LoginWithSSO, Settings - pages/_app.tsx - pages/index.tsx (marketing landing) - pages/app/{index,login,settings,sso}.tsx Notable cleanups picked up by strict typing: - LoginWithSSO was using but Alert reads `success` — the alert always rendered as a failure regardless of outcome. Fixed. - pages/app/{login,sso}.tsx had `e` shadowing onSubmit's parameter in catch blocks. Replaced class methods with arrow-function properties + `catch (err)`, dropping the inner .bind(this). - Settings now has a proper Intent union type instead of a stringly- typed `intent` parameter. - All class component renders carry the `override` modifier required by tsconfig's `noImplicitOverride`. globals.d.ts adds `declare module '*.css'` for side-effect CSS imports (the previous tailwind.css import in _app was unrecognised by tsc). The 4 config files (next.config.js, instrumentation-client.js, tailwind.config.js, postcss.config.js) intentionally stay JS — no benefit to converting them. Verified: typecheck (strict, 0 errors), lint (0 warnings), format, build all clean. --- components/{Alert.js => Alert.tsx} | 9 +- components/{Home.js => Home.tsx} | 5 +- components/{Layout.js => Layout.tsx} | 13 +- .../{LoginWithEmail.js => LoginWithEmail.tsx} | 12 +- .../{LoginWithSSO.js => LoginWithSSO.tsx} | 14 +- components/{Settings.js => Settings.tsx} | 15 +- globals.d.ts | 1 + pages/{_app.js => _app.tsx} | 3 +- pages/app/{index.js => index.tsx} | 2 +- pages/app/{login.js => login.tsx} | 21 +- pages/app/{settings.js => settings.tsx} | 19 +- pages/app/{sso.js => sso.tsx} | 19 +- pages/index.js | 185 ------------------ pages/index.tsx | 179 +++++++++++++++++ 14 files changed, 268 insertions(+), 229 deletions(-) rename components/{Alert.js => Alert.tsx} (82%) rename components/{Home.js => Home.tsx} (99%) rename components/{Layout.js => Layout.tsx} (97%) rename components/{LoginWithEmail.js => LoginWithEmail.tsx} (87%) rename components/{LoginWithSSO.js => LoginWithSSO.tsx} (84%) rename components/{Settings.js => Settings.tsx} (94%) create mode 100644 globals.d.ts rename pages/{_app.js => _app.tsx} (69%) rename pages/app/{index.js => index.tsx} (95%) rename pages/app/{login.js => login.tsx} (70%) rename pages/app/{settings.js => settings.tsx} (71%) rename pages/app/{sso.js => sso.tsx} (73%) delete mode 100644 pages/index.js create mode 100644 pages/index.tsx diff --git a/components/Alert.js b/components/Alert.tsx similarity index 82% rename from components/Alert.js rename to components/Alert.tsx index c38ae27..f75705c 100644 --- a/components/Alert.js +++ b/components/Alert.tsx @@ -1,8 +1,13 @@ import React from 'react' import { CheckCircleIcon, XCircleIcon } from '@heroicons/react/24/solid' -export default class Alert extends React.Component { - render() { +type AlertProps = { + success: boolean | null + message: string | null +} + +export default class Alert extends React.Component { + override render() { const icon = this.props.success ? ( ) : ( diff --git a/components/Home.js b/components/Home.tsx similarity index 99% rename from components/Home.js rename to components/Home.tsx index 3f6445b..7a860d7 100644 --- a/components/Home.js +++ b/components/Home.tsx @@ -87,9 +87,8 @@ const comments = [ }, ] -function classNames(...classes) { - return classes.filter(Boolean).join(' ') -} +const classNames = (...classes: (string | false | null | undefined)[]) => + classes.filter(Boolean).join(' ') export default function Home() { return ( diff --git a/components/Layout.js b/components/Layout.tsx similarity index 97% rename from components/Layout.js rename to components/Layout.tsx index 53e5f5c..7faa361 100644 --- a/components/Layout.js +++ b/components/Layout.tsx @@ -1,9 +1,13 @@ -import { Fragment } from 'react' +import { Fragment, type ReactNode } from 'react' import Link from 'next/link' import { Menu, Popover, Transition } from '@headlessui/react' import { MagnifyingGlassIcon } from '@heroicons/react/24/solid' import { BellIcon, Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline' +type LayoutProps = { + children: ReactNode +} + const user = { name: 'Whitney Francis', email: 'whitney@example.com', @@ -17,11 +21,10 @@ const navigation = [ { name: 'Company', href: '#' }, ] -function classNames(...classes) { - return classes.filter(Boolean).join(' ') -} +const classNames = (...classes: (string | false | null | undefined)[]) => + classes.filter(Boolean).join(' ') -export default function Layout(props) { +export default function Layout(props: LayoutProps) { const userNavigation = [ { name: 'Admin Settings', href: '/app/settings' }, { name: 'Your Profile', href: '#' }, diff --git a/components/LoginWithEmail.js b/components/LoginWithEmail.tsx similarity index 87% rename from components/LoginWithEmail.js rename to components/LoginWithEmail.tsx index c73f576..f535c44 100644 --- a/components/LoginWithEmail.js +++ b/components/LoginWithEmail.tsx @@ -3,8 +3,14 @@ import Link from 'next/link' import { EnvelopeIcon } from '@heroicons/react/24/solid' import Alert from './Alert' -export default class LoginWithEmail extends React.Component { - render() { +type LoginWithEmailProps = { + onSubmit: (e: React.FormEvent) => void | Promise + success: boolean | null + message: string | null +} + +export default class LoginWithEmail extends React.Component { + override render() { return (
@@ -18,7 +24,7 @@ export default class LoginWithEmail extends React.Component {
-
+