From 57e994fb1554cad6f1a1b220407eed936b8de6f5 Mon Sep 17 00:00:00 2001 From: Wendell Adriel Date: Wed, 21 Jan 2026 19:11:30 +0000 Subject: [PATCH] TypeScript types refactor (#252) * TypeScript types refactor * Remove not used type * Refactor Typescript types * Refactor interfaces * Update eslint-plugin-import configuration * Co-locate types for hooks --- eslint.config.js | 12 +++++- resources/js/app.tsx | 4 +- resources/js/components/alert-error.tsx | 3 +- resources/js/components/app-content.tsx | 13 ++---- resources/js/components/app-header.tsx | 18 ++++---- resources/js/components/app-logo-icon.tsx | 2 +- resources/js/components/app-shell.tsx | 14 +++---- .../js/components/app-sidebar-header.tsx | 2 +- resources/js/components/app-sidebar.tsx | 8 ++-- resources/js/components/appearance-tabs.tsx | 9 ++-- resources/js/components/breadcrumbs.tsx | 7 ++-- resources/js/components/delete-user.tsx | 5 +-- resources/js/components/input-error.tsx | 3 +- resources/js/components/nav-footer.tsx | 5 +-- resources/js/components/nav-main.tsx | 5 +-- resources/js/components/nav-user.tsx | 7 ++-- resources/js/components/text-link.tsx | 9 ++-- .../components/two-factor-recovery-codes.tsx | 14 +++---- .../js/components/two-factor-setup-modal.tsx | 16 ++++--- resources/js/components/ui/icon.tsx | 2 +- resources/js/components/ui/sidebar.tsx | 3 +- resources/js/components/user-info.tsx | 2 +- resources/js/components/user-menu-content.tsx | 13 +++--- resources/js/hooks/use-appearance.tsx | 8 +++- resources/js/hooks/use-clipboard.ts | 8 ++-- resources/js/hooks/use-current-url.ts | 36 +++++++++++----- resources/js/hooks/use-initials.tsx | 4 +- resources/js/hooks/use-mobile-navigation.ts | 4 +- resources/js/hooks/use-two-factor-auth.ts | 27 +++++++----- resources/js/layouts/app-layout.tsx | 9 +--- .../js/layouts/app/app-header-layout.tsx | 6 +-- .../js/layouts/app/app-sidebar-layout.tsx | 6 +-- .../js/layouts/auth/auth-card-layout.tsx | 5 +-- .../js/layouts/auth/auth-simple-layout.tsx | 13 ++---- .../js/layouts/auth/auth-split-layout.tsx | 13 ++---- resources/js/layouts/settings/layout.tsx | 7 ++-- resources/js/lib/utils.ts | 2 +- resources/js/pages/auth/confirm-password.tsx | 3 +- resources/js/pages/auth/forgot-password.tsx | 5 +-- resources/js/pages/auth/login.tsx | 9 ++-- resources/js/pages/auth/register.tsx | 3 +- resources/js/pages/auth/reset-password.tsx | 9 ++-- .../js/pages/auth/two-factor-challenge.tsx | 7 ++-- resources/js/pages/auth/verify-email.tsx | 3 +- resources/js/pages/dashboard.tsx | 5 +-- resources/js/pages/settings/appearance.tsx | 5 +-- resources/js/pages/settings/password.tsx | 9 ++-- resources/js/pages/settings/profile.tsx | 7 ++-- resources/js/pages/settings/two-factor.tsx | 15 ++++--- resources/js/pages/welcome.tsx | 5 +-- resources/js/types/auth.ts | 24 +++++++++++ resources/js/types/index.d.ts | 42 ------------------- resources/js/types/index.ts | 12 ++++++ resources/js/types/navigation.ts | 14 +++++++ resources/js/types/ui.ts | 14 +++++++ 55 files changed, 257 insertions(+), 258 deletions(-) create mode 100644 resources/js/types/auth.ts delete mode 100644 resources/js/types/index.d.ts create mode 100644 resources/js/types/index.ts create mode 100644 resources/js/types/navigation.ts create mode 100644 resources/js/types/ui.ts diff --git a/eslint.config.js b/eslint.config.js index 5633bc3b1..74baa3e68 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -43,7 +43,6 @@ export default [ 'error', { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], - 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true, @@ -55,9 +54,18 @@ export default [ { ...importPlugin.flatConfigs.typescript, files: ['**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'separate-type-imports', + }, + ], + }, }, { - ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js'], + ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'vite.config.ts'], }, prettier, // Turn off all rules that might conflict with Prettier ]; diff --git a/resources/js/app.tsx b/resources/js/app.tsx index f78daf42a..457905c31 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -1,10 +1,8 @@ -import '../css/app.css'; - import { createInertiaApp } from '@inertiajs/react'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; - +import '../css/app.css'; import { initializeTheme } from './hooks/use-appearance'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; diff --git a/resources/js/components/alert-error.tsx b/resources/js/components/alert-error.tsx index c26cc944c..8cc228bd4 100644 --- a/resources/js/components/alert-error.tsx +++ b/resources/js/components/alert-error.tsx @@ -1,6 +1,5 @@ -import { AlertCircleIcon } from 'lucide-react'; - import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { AlertCircleIcon } from 'lucide-react'; export default function AlertError({ errors, diff --git a/resources/js/components/app-content.tsx b/resources/js/components/app-content.tsx index bfc77bc21..e5c9989b3 100644 --- a/resources/js/components/app-content.tsx +++ b/resources/js/components/app-content.tsx @@ -1,16 +1,11 @@ -import * as React from 'react'; - import { SidebarInset } from '@/components/ui/sidebar'; +import * as React from 'react'; -interface AppContentProps extends React.ComponentProps<'main'> { +type Props = React.ComponentProps<'main'> & { variant?: 'header' | 'sidebar'; -} +}; -export function AppContent({ - variant = 'header', - children, - ...props -}: AppContentProps) { +export function AppContent({ variant = 'header', children, ...props }: Props) { if (variant === 'sidebar') { return {children}; } diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx index b02f2cfd4..e9b34295f 100644 --- a/resources/js/components/app-header.tsx +++ b/resources/js/components/app-header.tsx @@ -1,6 +1,3 @@ -import { Link, usePage } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react'; - import { Breadcrumbs } from '@/components/breadcrumbs'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; @@ -33,11 +30,16 @@ import { useCurrentUrl } from '@/hooks/use-current-url'; import { useInitials } from '@/hooks/use-initials'; import { cn, toUrl } from '@/lib/utils'; import { dashboard } from '@/routes'; -import { type BreadcrumbItem, type NavItem, type SharedData } from '@/types'; - +import type { BreadcrumbItem, NavItem, SharedData } from '@/types'; +import { Link, usePage } from '@inertiajs/react'; +import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react'; import AppLogo from './app-logo'; import AppLogoIcon from './app-logo-icon'; +type Props = { + breadcrumbs?: BreadcrumbItem[]; +}; + const mainNavItems: NavItem[] = [ { title: 'Dashboard', @@ -62,11 +64,7 @@ const rightNavItems: NavItem[] = [ const activeItemStyles = 'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'; -interface AppHeaderProps { - breadcrumbs?: BreadcrumbItem[]; -} - -export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) { +export function AppHeader({ breadcrumbs = [] }: Props) { const page = usePage(); const { auth } = page.props; const getInitials = useInitials(); diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx index 9bd62ad84..0c3121533 100644 --- a/resources/js/components/app-logo-icon.tsx +++ b/resources/js/components/app-logo-icon.tsx @@ -1,4 +1,4 @@ -import { SVGAttributes } from 'react'; +import type { SVGAttributes } from 'react'; export default function AppLogoIcon(props: SVGAttributes) { return ( diff --git a/resources/js/components/app-shell.tsx b/resources/js/components/app-shell.tsx index c89423a42..c32a6046c 100644 --- a/resources/js/components/app-shell.tsx +++ b/resources/js/components/app-shell.tsx @@ -1,14 +1,14 @@ -import { usePage } from '@inertiajs/react'; - import { SidebarProvider } from '@/components/ui/sidebar'; -import { SharedData } from '@/types'; +import type { SharedData } from '@/types'; +import { usePage } from '@inertiajs/react'; +import type { ReactNode } from 'react'; -interface AppShellProps { - children: React.ReactNode; +type Props = { + children: ReactNode; variant?: 'header' | 'sidebar'; -} +}; -export function AppShell({ children, variant = 'header' }: AppShellProps) { +export function AppShell({ children, variant = 'header' }: Props) { const isOpen = usePage().props.sidebarOpen; if (variant === 'header') { diff --git a/resources/js/components/app-sidebar-header.tsx b/resources/js/components/app-sidebar-header.tsx index a779df7f1..9d53d6c91 100644 --- a/resources/js/components/app-sidebar-header.tsx +++ b/resources/js/components/app-sidebar-header.tsx @@ -1,6 +1,6 @@ import { Breadcrumbs } from '@/components/breadcrumbs'; import { SidebarTrigger } from '@/components/ui/sidebar'; -import { type BreadcrumbItem as BreadcrumbItemType } from '@/types'; +import type { BreadcrumbItem as BreadcrumbItemType } from '@/types'; export function AppSidebarHeader({ breadcrumbs = [], diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 70adb3529..a973ba22e 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,6 +1,3 @@ -import { Link } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid } from 'lucide-react'; - import { NavFooter } from '@/components/nav-footer'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; @@ -14,8 +11,9 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { dashboard } from '@/routes'; -import { type NavItem } from '@/types'; - +import type { NavItem } from '@/types'; +import { Link } from '@inertiajs/react'; +import { BookOpen, Folder, LayoutGrid } from 'lucide-react'; import AppLogo from './app-logo'; const mainNavItems: NavItem[] = [ diff --git a/resources/js/components/appearance-tabs.tsx b/resources/js/components/appearance-tabs.tsx index fe141bfc2..0a30d23d8 100644 --- a/resources/js/components/appearance-tabs.tsx +++ b/resources/js/components/appearance-tabs.tsx @@ -1,8 +1,9 @@ -import { LucideIcon, Monitor, Moon, Sun } from 'lucide-react'; -import { HTMLAttributes } from 'react'; - -import { Appearance, useAppearance } from '@/hooks/use-appearance'; +import type { Appearance } from '@/hooks/use-appearance'; +import { useAppearance } from '@/hooks/use-appearance'; import { cn } from '@/lib/utils'; +import type { LucideIcon } from 'lucide-react'; +import { Monitor, Moon, Sun } from 'lucide-react'; +import type { HTMLAttributes } from 'react'; export default function AppearanceToggleTab({ className = '', diff --git a/resources/js/components/breadcrumbs.tsx b/resources/js/components/breadcrumbs.tsx index 213e4253e..43ea0a9e8 100644 --- a/resources/js/components/breadcrumbs.tsx +++ b/resources/js/components/breadcrumbs.tsx @@ -1,6 +1,3 @@ -import { Link } from '@inertiajs/react'; -import { Fragment } from 'react'; - import { Breadcrumb, BreadcrumbItem, @@ -9,7 +6,9 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; -import { type BreadcrumbItem as BreadcrumbItemType } from '@/types'; +import type { BreadcrumbItem as BreadcrumbItemType } from '@/types'; +import { Link } from '@inertiajs/react'; +import { Fragment } from 'react'; export function Breadcrumbs({ breadcrumbs, diff --git a/resources/js/components/delete-user.tsx b/resources/js/components/delete-user.tsx index 201ccaa02..7fbd03d5c 100644 --- a/resources/js/components/delete-user.tsx +++ b/resources/js/components/delete-user.tsx @@ -1,6 +1,3 @@ -import { Form } from '@inertiajs/react'; -import { useRef } from 'react'; - import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController'; import Heading from '@/components/heading'; import InputError from '@/components/input-error'; @@ -16,6 +13,8 @@ import { } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { Form } from '@inertiajs/react'; +import { useRef } from 'react'; export default function DeleteUser() { const passwordInput = useRef(null); diff --git a/resources/js/components/input-error.tsx b/resources/js/components/input-error.tsx index 14c21a2fb..9b36b312f 100644 --- a/resources/js/components/input-error.tsx +++ b/resources/js/components/input-error.tsx @@ -1,6 +1,5 @@ -import { type HTMLAttributes } from 'react'; - import { cn } from '@/lib/utils'; +import type { HTMLAttributes } from 'react'; export default function InputError({ message, diff --git a/resources/js/components/nav-footer.tsx b/resources/js/components/nav-footer.tsx index b2ac1ee0b..b19d1596e 100644 --- a/resources/js/components/nav-footer.tsx +++ b/resources/js/components/nav-footer.tsx @@ -1,5 +1,3 @@ -import { type ComponentPropsWithoutRef } from 'react'; - import { SidebarGroup, SidebarGroupContent, @@ -8,7 +6,8 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { toUrl } from '@/lib/utils'; -import { type NavItem } from '@/types'; +import type { NavItem } from '@/types'; +import type { ComponentPropsWithoutRef } from 'react'; export function NavFooter({ items, diff --git a/resources/js/components/nav-main.tsx b/resources/js/components/nav-main.tsx index cebd7c658..018410d97 100644 --- a/resources/js/components/nav-main.tsx +++ b/resources/js/components/nav-main.tsx @@ -1,5 +1,3 @@ -import { Link } from '@inertiajs/react'; - import { SidebarGroup, SidebarGroupLabel, @@ -8,7 +6,8 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { useCurrentUrl } from '@/hooks/use-current-url'; -import { type NavItem } from '@/types'; +import type { NavItem } from '@/types'; +import { Link } from '@inertiajs/react'; export function NavMain({ items = [] }: { items: NavItem[] }) { const { isCurrentUrl } = useCurrentUrl(); diff --git a/resources/js/components/nav-user.tsx b/resources/js/components/nav-user.tsx index 2fc836f5d..f8c5f0b78 100644 --- a/resources/js/components/nav-user.tsx +++ b/resources/js/components/nav-user.tsx @@ -1,6 +1,3 @@ -import { usePage } from '@inertiajs/react'; -import { ChevronsUpDown } from 'lucide-react'; - import { DropdownMenu, DropdownMenuContent, @@ -15,7 +12,9 @@ import { import { UserInfo } from '@/components/user-info'; import { UserMenuContent } from '@/components/user-menu-content'; import { useIsMobile } from '@/hooks/use-mobile'; -import { type SharedData } from '@/types'; +import type { SharedData } from '@/types'; +import { usePage } from '@inertiajs/react'; +import { ChevronsUpDown } from 'lucide-react'; export function NavUser() { const { auth } = usePage().props; diff --git a/resources/js/components/text-link.tsx b/resources/js/components/text-link.tsx index 1eae8d729..78d06b397 100644 --- a/resources/js/components/text-link.tsx +++ b/resources/js/components/text-link.tsx @@ -1,15 +1,14 @@ -import { Link } from '@inertiajs/react'; -import { ComponentProps } from 'react'; - import { cn } from '@/lib/utils'; +import { Link } from '@inertiajs/react'; +import type { ComponentProps } from 'react'; -type LinkProps = ComponentProps; +type Props = ComponentProps; export default function TextLink({ className = '', children, ...props -}: LinkProps) { +}: Props) { return ( Promise; errors: string[]; -} +}; export default function TwoFactorRecoveryCodes({ recoveryCodesList, fetchRecoveryCodes, errors, -}: TwoFactorRecoveryCodesProps) { +}: Props) { const [codesAreVisible, setCodesAreVisible] = useState(false); const codesSectionRef = useRef(null); const canRegenerateCodes = recoveryCodesList.length > 0 && codesAreVisible; diff --git a/resources/js/components/two-factor-setup-modal.tsx b/resources/js/components/two-factor-setup-modal.tsx index f3af15d95..84ea60deb 100644 --- a/resources/js/components/two-factor-setup-modal.tsx +++ b/resources/js/components/two-factor-setup-modal.tsx @@ -1,8 +1,3 @@ -import { Form } from '@inertiajs/react'; -import { REGEXP_ONLY_DIGITS } from 'input-otp'; -import { Check, Copy, ScanLine } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; - import InputError from '@/components/input-error'; import { Button } from '@/components/ui/button'; import { @@ -21,7 +16,10 @@ import { useAppearance } from '@/hooks/use-appearance'; import { useClipboard } from '@/hooks/use-clipboard'; import { OTP_MAX_LENGTH } from '@/hooks/use-two-factor-auth'; import { confirm } from '@/routes/two-factor'; - +import { Form } from '@inertiajs/react'; +import { REGEXP_ONLY_DIGITS } from 'input-otp'; +import { Check, Copy, ScanLine } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import AlertError from './alert-error'; import { Spinner } from './ui/spinner'; @@ -230,7 +228,7 @@ function TwoFactorVerificationStep({ ); } -interface TwoFactorSetupModalProps { +type Props = { isOpen: boolean; onClose: () => void; requiresConfirmation: boolean; @@ -240,7 +238,7 @@ interface TwoFactorSetupModalProps { clearSetupData: () => void; fetchSetupData: () => Promise; errors: string[]; -} +}; export default function TwoFactorSetupModal({ isOpen, @@ -252,7 +250,7 @@ export default function TwoFactorSetupModal({ clearSetupData, fetchSetupData, errors, -}: TwoFactorSetupModalProps) { +}: Props) { const [showVerificationStep, setShowVerificationStep] = useState(false); diff --git a/resources/js/components/ui/icon.tsx b/resources/js/components/ui/icon.tsx index bb8b3a068..b9bd3b265 100644 --- a/resources/js/components/ui/icon.tsx +++ b/resources/js/components/ui/icon.tsx @@ -1,4 +1,4 @@ -import { LucideIcon } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; interface IconProps { iconNode?: LucideIcon | null; diff --git a/resources/js/components/ui/sidebar.tsx b/resources/js/components/ui/sidebar.tsx index 9a5bc3740..a213b29c5 100644 --- a/resources/js/components/ui/sidebar.tsx +++ b/resources/js/components/ui/sidebar.tsx @@ -1,5 +1,6 @@ import { Slot } from "@radix-ui/react-slot" -import { VariantProps, cva } from "class-variance-authority" +import type { VariantProps} from "class-variance-authority"; +import { cva } from "class-variance-authority" import { PanelLeftCloseIcon, PanelLeftOpenIcon } from "lucide-react" import * as React from "react" diff --git a/resources/js/components/user-info.tsx b/resources/js/components/user-info.tsx index 8fca44c4c..dd0848e84 100644 --- a/resources/js/components/user-info.tsx +++ b/resources/js/components/user-info.tsx @@ -1,6 +1,6 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { useInitials } from '@/hooks/use-initials'; -import { type User } from '@/types'; +import type { User } from '@/types'; export function UserInfo({ user, diff --git a/resources/js/components/user-menu-content.tsx b/resources/js/components/user-menu-content.tsx index 4931ff2df..b1578c6c2 100644 --- a/resources/js/components/user-menu-content.tsx +++ b/resources/js/components/user-menu-content.tsx @@ -1,6 +1,3 @@ -import { Link, router } from '@inertiajs/react'; -import { LogOut, Settings } from 'lucide-react'; - import { DropdownMenuGroup, DropdownMenuItem, @@ -11,13 +8,15 @@ import { UserInfo } from '@/components/user-info'; import { useMobileNavigation } from '@/hooks/use-mobile-navigation'; import { logout } from '@/routes'; import { edit } from '@/routes/profile'; -import { type User } from '@/types'; +import type { User } from '@/types'; +import { Link, router } from '@inertiajs/react'; +import { LogOut, Settings } from 'lucide-react'; -interface UserMenuContentProps { +type Props = { user: User; -} +}; -export function UserMenuContent({ user }: UserMenuContentProps) { +export function UserMenuContent({ user }: Props) { const cleanup = useMobileNavigation(); const handleLogout = () => { diff --git a/resources/js/hooks/use-appearance.tsx b/resources/js/hooks/use-appearance.tsx index feb9046ea..4e774711c 100644 --- a/resources/js/hooks/use-appearance.tsx +++ b/resources/js/hooks/use-appearance.tsx @@ -3,6 +3,12 @@ import { useCallback, useMemo, useSyncExternalStore } from 'react'; export type ResolvedAppearance = 'light' | 'dark'; export type Appearance = ResolvedAppearance | 'system'; +export type UseAppearanceReturn = { + readonly appearance: Appearance; + readonly resolvedAppearance: ResolvedAppearance; + readonly updateAppearance: (mode: Appearance) => void; +}; + const listeners = new Set<() => void>(); let currentAppearance: Appearance = 'system'; @@ -71,7 +77,7 @@ export function initializeTheme(): void { mediaQuery()?.addEventListener('change', handleSystemThemeChange); } -export function useAppearance() { +export function useAppearance(): UseAppearanceReturn { const appearance: Appearance = useSyncExternalStore( subscribe, () => currentAppearance, diff --git a/resources/js/hooks/use-clipboard.ts b/resources/js/hooks/use-clipboard.ts index eef514b0e..ebc3d7775 100644 --- a/resources/js/hooks/use-clipboard.ts +++ b/resources/js/hooks/use-clipboard.ts @@ -1,11 +1,11 @@ // Credit: https://usehooks-ts.com/ import { useCallback, useState } from 'react'; -type CopiedValue = string | null; +export type CopiedValue = string | null; +export type CopyFn = (text: string) => Promise; +export type UseClipboardReturn = [CopiedValue, CopyFn]; -type CopyFn = (text: string) => Promise; - -export function useClipboard(): [CopiedValue, CopyFn] { +export function useClipboard(): UseClipboardReturn { const [copiedText, setCopiedText] = useState(null); const copy: CopyFn = useCallback(async (text) => { diff --git a/resources/js/hooks/use-current-url.ts b/resources/js/hooks/use-current-url.ts index 7e119bd6e..8aaa0a3a1 100644 --- a/resources/js/hooks/use-current-url.ts +++ b/resources/js/hooks/use-current-url.ts @@ -1,16 +1,32 @@ +import { toUrl } from '@/lib/utils'; import type { InertiaLinkProps } from '@inertiajs/react'; import { usePage } from '@inertiajs/react'; -import { toUrl } from '@/lib/utils'; +export type IsCurrentUrlFn = ( + urlToCheck: NonNullable, + currentUrl?: string, +) => boolean; + +export type WhenCurrentUrlFn = ( + urlToCheck: NonNullable, + ifTrue: TIfTrue, + ifFalse?: TIfFalse, +) => TIfTrue | TIfFalse; -export function useCurrentUrl() { +export type UseCurrentUrlReturn = { + currentUrl: string; + isCurrentUrl: IsCurrentUrlFn; + whenCurrentUrl: WhenCurrentUrlFn; +}; + +export function useCurrentUrl(): UseCurrentUrlReturn { const page = usePage(); const currentUrlPath = new URL(page.url, window?.location.origin).pathname; - function isCurrentUrl( + const isCurrentUrl: IsCurrentUrlFn = ( urlToCheck: NonNullable, currentUrl?: string, - ) { + ) => { const urlToCompare = currentUrl ?? currentUrlPath; const urlString = toUrl(urlToCheck); @@ -24,15 +40,15 @@ export function useCurrentUrl() { } catch { return false; } - } + }; - function whenCurrentUrl( + const whenCurrentUrl: WhenCurrentUrlFn = ( urlToCheck: NonNullable, - ifTrue, - ifFalse = null, - ) { + ifTrue: TIfTrue, + ifFalse: TIfFalse = null as TIfFalse, + ): TIfTrue | TIfFalse => { return isCurrentUrl(urlToCheck) ? ifTrue : ifFalse; - } + }; return { currentUrl: currentUrlPath, diff --git a/resources/js/hooks/use-initials.tsx b/resources/js/hooks/use-initials.tsx index d09d2f8a1..f5bcaaff7 100644 --- a/resources/js/hooks/use-initials.tsx +++ b/resources/js/hooks/use-initials.tsx @@ -1,6 +1,8 @@ import { useCallback } from 'react'; -export function useInitials() { +export type GetInitialsFn = (fullName: string) => string; + +export function useInitials(): GetInitialsFn { return useCallback((fullName: string): string => { const names = fullName.trim().split(' '); diff --git a/resources/js/hooks/use-mobile-navigation.ts b/resources/js/hooks/use-mobile-navigation.ts index cfd5ee939..834b9c9c0 100644 --- a/resources/js/hooks/use-mobile-navigation.ts +++ b/resources/js/hooks/use-mobile-navigation.ts @@ -1,6 +1,8 @@ import { useCallback } from 'react'; -export function useMobileNavigation() { +export type CleanupFn = () => void; + +export function useMobileNavigation(): CleanupFn { return useCallback(() => { // Remove pointer-events style from body... document.body.style.removeProperty('pointer-events'); diff --git a/resources/js/hooks/use-two-factor-auth.ts b/resources/js/hooks/use-two-factor-auth.ts index 369b86e83..8f09080a9 100644 --- a/resources/js/hooks/use-two-factor-auth.ts +++ b/resources/js/hooks/use-two-factor-auth.ts @@ -1,15 +1,20 @@ -import { useCallback, useMemo, useState } from 'react'; - import { qrCode, recoveryCodes, secretKey } from '@/routes/two-factor'; +import type { TwoFactorSecretKey, TwoFactorSetupData } from '@/types'; +import { useCallback, useMemo, useState } from 'react'; -interface TwoFactorSetupData { - svg: string; - url: string; -} - -interface TwoFactorSecretKey { - secretKey: string; -} +export type UseTwoFactorAuthReturn = { + qrCodeSvg: string | null; + manualSetupKey: string | null; + recoveryCodesList: string[]; + hasSetupData: boolean; + errors: string[]; + clearErrors: () => void; + clearSetupData: () => void; + fetchQrCode: () => Promise; + fetchSetupKey: () => Promise; + fetchSetupData: () => Promise; + fetchRecoveryCodes: () => Promise; +}; export const OTP_MAX_LENGTH = 6; @@ -25,7 +30,7 @@ const fetchJson = async (url: string): Promise => { return response.json(); }; -export const useTwoFactorAuth = () => { +export const useTwoFactorAuth = (): UseTwoFactorAuthReturn => { const [qrCodeSvg, setQrCodeSvg] = useState(null); const [manualSetupKey, setManualSetupKey] = useState(null); const [recoveryCodesList, setRecoveryCodesList] = useState([]); diff --git a/resources/js/layouts/app-layout.tsx b/resources/js/layouts/app-layout.tsx index 9c45138f3..099a70672 100644 --- a/resources/js/layouts/app-layout.tsx +++ b/resources/js/layouts/app-layout.tsx @@ -1,12 +1,5 @@ -import { type ReactNode } from 'react'; - import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout'; -import { type BreadcrumbItem } from '@/types'; - -interface AppLayoutProps { - children: ReactNode; - breadcrumbs?: BreadcrumbItem[]; -} +import type { AppLayoutProps } from '@/types'; export default ({ children, breadcrumbs, ...props }: AppLayoutProps) => ( diff --git a/resources/js/layouts/app/app-header-layout.tsx b/resources/js/layouts/app/app-header-layout.tsx index 227a2f2cf..db0e7bb04 100644 --- a/resources/js/layouts/app/app-header-layout.tsx +++ b/resources/js/layouts/app/app-header-layout.tsx @@ -1,14 +1,12 @@ -import type { PropsWithChildren } from 'react'; - import { AppContent } from '@/components/app-content'; import { AppHeader } from '@/components/app-header'; import { AppShell } from '@/components/app-shell'; -import { type BreadcrumbItem } from '@/types'; +import type { AppLayoutProps } from '@/types'; export default function AppHeaderLayout({ children, breadcrumbs, -}: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) { +}: AppLayoutProps) { return ( diff --git a/resources/js/layouts/app/app-sidebar-layout.tsx b/resources/js/layouts/app/app-sidebar-layout.tsx index 9c0b17225..a6edb6260 100644 --- a/resources/js/layouts/app/app-sidebar-layout.tsx +++ b/resources/js/layouts/app/app-sidebar-layout.tsx @@ -1,15 +1,13 @@ -import { type PropsWithChildren } from 'react'; - import { AppContent } from '@/components/app-content'; import { AppShell } from '@/components/app-shell'; import { AppSidebar } from '@/components/app-sidebar'; import { AppSidebarHeader } from '@/components/app-sidebar-header'; -import { type BreadcrumbItem } from '@/types'; +import type { AppLayoutProps } from '@/types'; export default function AppSidebarLayout({ children, breadcrumbs = [], -}: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) { +}: AppLayoutProps) { return ( diff --git a/resources/js/layouts/auth/auth-card-layout.tsx b/resources/js/layouts/auth/auth-card-layout.tsx index 814e15bd9..f8aad9523 100644 --- a/resources/js/layouts/auth/auth-card-layout.tsx +++ b/resources/js/layouts/auth/auth-card-layout.tsx @@ -1,6 +1,3 @@ -import { Link } from '@inertiajs/react'; -import { type PropsWithChildren } from 'react'; - import AppLogoIcon from '@/components/app-logo-icon'; import { Card, @@ -10,6 +7,8 @@ import { CardTitle, } from '@/components/ui/card'; import { home } from '@/routes'; +import { Link } from '@inertiajs/react'; +import type { PropsWithChildren } from 'react'; export default function AuthCardLayout({ children, diff --git a/resources/js/layouts/auth/auth-simple-layout.tsx b/resources/js/layouts/auth/auth-simple-layout.tsx index 13cc79b37..a81715bde 100644 --- a/resources/js/layouts/auth/auth-simple-layout.tsx +++ b/resources/js/layouts/auth/auth-simple-layout.tsx @@ -1,20 +1,13 @@ -import { Link } from '@inertiajs/react'; -import { type PropsWithChildren } from 'react'; - import AppLogoIcon from '@/components/app-logo-icon'; import { home } from '@/routes'; - -interface AuthLayoutProps { - name?: string; - title?: string; - description?: string; -} +import type { AuthLayoutProps } from '@/types'; +import { Link } from '@inertiajs/react'; export default function AuthSimpleLayout({ children, title, description, -}: PropsWithChildren) { +}: AuthLayoutProps) { return (
diff --git a/resources/js/layouts/auth/auth-split-layout.tsx b/resources/js/layouts/auth/auth-split-layout.tsx index be8ee87ef..4bac15d80 100644 --- a/resources/js/layouts/auth/auth-split-layout.tsx +++ b/resources/js/layouts/auth/auth-split-layout.tsx @@ -1,20 +1,13 @@ -import { Link, usePage } from '@inertiajs/react'; -import { type PropsWithChildren } from 'react'; - import AppLogoIcon from '@/components/app-logo-icon'; import { home } from '@/routes'; -import { type SharedData } from '@/types'; - -interface AuthLayoutProps { - title?: string; - description?: string; -} +import type { AuthLayoutProps, SharedData } from '@/types'; +import { Link, usePage } from '@inertiajs/react'; export default function AuthSplitLayout({ children, title, description, -}: PropsWithChildren) { +}: AuthLayoutProps) { const { name } = usePage().props; return ( diff --git a/resources/js/layouts/settings/layout.tsx b/resources/js/layouts/settings/layout.tsx index fab634fe3..715831f8b 100644 --- a/resources/js/layouts/settings/layout.tsx +++ b/resources/js/layouts/settings/layout.tsx @@ -1,6 +1,3 @@ -import { Link } from '@inertiajs/react'; -import { type PropsWithChildren } from 'react'; - import Heading from '@/components/heading'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; @@ -10,7 +7,9 @@ import { edit as editAppearance } from '@/routes/appearance'; import { edit } from '@/routes/profile'; import { show } from '@/routes/two-factor'; import { edit as editPassword } from '@/routes/user-password'; -import { type NavItem } from '@/types'; +import type { NavItem } from '@/types'; +import { Link } from '@inertiajs/react'; +import type { PropsWithChildren } from 'react'; const sidebarNavItems: NavItem[] = [ { diff --git a/resources/js/lib/utils.ts b/resources/js/lib/utils.ts index 70f56f154..d6fae85d7 100644 --- a/resources/js/lib/utils.ts +++ b/resources/js/lib/utils.ts @@ -1,4 +1,4 @@ -import { InertiaLinkProps } from '@inertiajs/react'; +import type { InertiaLinkProps } from '@inertiajs/react'; import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; diff --git a/resources/js/pages/auth/confirm-password.tsx b/resources/js/pages/auth/confirm-password.tsx index f4461e6c9..b9ae2690d 100644 --- a/resources/js/pages/auth/confirm-password.tsx +++ b/resources/js/pages/auth/confirm-password.tsx @@ -1,5 +1,3 @@ -import { Form, Head } from '@inertiajs/react'; - import InputError from '@/components/input-error'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -7,6 +5,7 @@ import { Label } from '@/components/ui/label'; import { Spinner } from '@/components/ui/spinner'; import AuthLayout from '@/layouts/auth-layout'; import { store } from '@/routes/password/confirm'; +import { Form, Head } from '@inertiajs/react'; export default function ConfirmPassword() { return ( diff --git a/resources/js/pages/auth/forgot-password.tsx b/resources/js/pages/auth/forgot-password.tsx index 190aeb768..811624058 100644 --- a/resources/js/pages/auth/forgot-password.tsx +++ b/resources/js/pages/auth/forgot-password.tsx @@ -1,7 +1,4 @@ // Components -import { Form, Head } from '@inertiajs/react'; -import { LoaderCircle } from 'lucide-react'; - import InputError from '@/components/input-error'; import TextLink from '@/components/text-link'; import { Button } from '@/components/ui/button'; @@ -10,6 +7,8 @@ import { Label } from '@/components/ui/label'; import AuthLayout from '@/layouts/auth-layout'; import { login } from '@/routes'; import { email } from '@/routes/password'; +import { Form, Head } from '@inertiajs/react'; +import { LoaderCircle } from 'lucide-react'; export default function ForgotPassword({ status }: { status?: string }) { return ( diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx index 6d924c2ec..186ee770e 100644 --- a/resources/js/pages/auth/login.tsx +++ b/resources/js/pages/auth/login.tsx @@ -1,5 +1,3 @@ -import { Form, Head } from '@inertiajs/react'; - import InputError from '@/components/input-error'; import TextLink from '@/components/text-link'; import { Button } from '@/components/ui/button'; @@ -11,18 +9,19 @@ import AuthLayout from '@/layouts/auth-layout'; import { register } from '@/routes'; import { store } from '@/routes/login'; import { request } from '@/routes/password'; +import { Form, Head } from '@inertiajs/react'; -interface LoginProps { +type Props = { status?: string; canResetPassword: boolean; canRegister: boolean; -} +}; export default function Login({ status, canResetPassword, canRegister, -}: LoginProps) { +}: Props) { return ( (false); diff --git a/resources/js/pages/auth/verify-email.tsx b/resources/js/pages/auth/verify-email.tsx index fbc315e16..03ac55793 100644 --- a/resources/js/pages/auth/verify-email.tsx +++ b/resources/js/pages/auth/verify-email.tsx @@ -1,12 +1,11 @@ // Components -import { Form, Head } from '@inertiajs/react'; - import TextLink from '@/components/text-link'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; import AuthLayout from '@/layouts/auth-layout'; import { logout } from '@/routes'; import { send } from '@/routes/verification'; +import { Form, Head } from '@inertiajs/react'; export default function VerifyEmail({ status }: { status?: string }) { return ( diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 2ed65b5c6..95e8a9636 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -1,9 +1,8 @@ -import { Head } from '@inertiajs/react'; - import { PlaceholderPattern } from '@/components/ui/placeholder-pattern'; import AppLayout from '@/layouts/app-layout'; import { dashboard } from '@/routes'; -import { type BreadcrumbItem } from '@/types'; +import type { BreadcrumbItem } from '@/types'; +import { Head } from '@inertiajs/react'; const breadcrumbs: BreadcrumbItem[] = [ { diff --git a/resources/js/pages/settings/appearance.tsx b/resources/js/pages/settings/appearance.tsx index 25c79ee21..acc852524 100644 --- a/resources/js/pages/settings/appearance.tsx +++ b/resources/js/pages/settings/appearance.tsx @@ -1,11 +1,10 @@ -import { Head } from '@inertiajs/react'; - import AppearanceTabs from '@/components/appearance-tabs'; import Heading from '@/components/heading'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { edit as editAppearance } from '@/routes/appearance'; -import { type BreadcrumbItem } from '@/types'; +import type { BreadcrumbItem } from '@/types'; +import { Head } from '@inertiajs/react'; const breadcrumbs: BreadcrumbItem[] = [ { diff --git a/resources/js/pages/settings/password.tsx b/resources/js/pages/settings/password.tsx index 87669b73c..18f46b082 100644 --- a/resources/js/pages/settings/password.tsx +++ b/resources/js/pages/settings/password.tsx @@ -1,7 +1,3 @@ -import { Transition } from '@headlessui/react'; -import { Form, Head } from '@inertiajs/react'; -import { useRef } from 'react'; - import PasswordController from '@/actions/App/Http/Controllers/Settings/PasswordController'; import Heading from '@/components/heading'; import InputError from '@/components/input-error'; @@ -11,7 +7,10 @@ import { Label } from '@/components/ui/label'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { edit } from '@/routes/user-password'; -import { type BreadcrumbItem } from '@/types'; +import type { BreadcrumbItem } from '@/types'; +import { Transition } from '@headlessui/react'; +import { Form, Head } from '@inertiajs/react'; +import { useRef } from 'react'; const breadcrumbs: BreadcrumbItem[] = [ { diff --git a/resources/js/pages/settings/profile.tsx b/resources/js/pages/settings/profile.tsx index 69ef4fa48..834c613c4 100644 --- a/resources/js/pages/settings/profile.tsx +++ b/resources/js/pages/settings/profile.tsx @@ -1,6 +1,3 @@ -import { Transition } from '@headlessui/react'; -import { Form, Head, Link, usePage } from '@inertiajs/react'; - import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController'; import DeleteUser from '@/components/delete-user'; import Heading from '@/components/heading'; @@ -12,7 +9,9 @@ import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { edit } from '@/routes/profile'; import { send } from '@/routes/verification'; -import { type BreadcrumbItem, type SharedData } from '@/types'; +import type { BreadcrumbItem, SharedData } from '@/types'; +import { Transition } from '@headlessui/react'; +import { Form, Head, Link, usePage } from '@inertiajs/react'; const breadcrumbs: BreadcrumbItem[] = [ { diff --git a/resources/js/pages/settings/two-factor.tsx b/resources/js/pages/settings/two-factor.tsx index 0a741086d..31922e791 100644 --- a/resources/js/pages/settings/two-factor.tsx +++ b/resources/js/pages/settings/two-factor.tsx @@ -1,7 +1,3 @@ -import { Form, Head } from '@inertiajs/react'; -import { ShieldBan, ShieldCheck } from 'lucide-react'; -import { useState } from 'react'; - import Heading from '@/components/heading'; import TwoFactorRecoveryCodes from '@/components/two-factor-recovery-codes'; import TwoFactorSetupModal from '@/components/two-factor-setup-modal'; @@ -11,12 +7,15 @@ import { useTwoFactorAuth } from '@/hooks/use-two-factor-auth'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { disable, enable, show } from '@/routes/two-factor'; -import { type BreadcrumbItem } from '@/types'; +import type { BreadcrumbItem } from '@/types'; +import { Form, Head } from '@inertiajs/react'; +import { ShieldBan, ShieldCheck } from 'lucide-react'; +import { useState } from 'react'; -interface TwoFactorProps { +type Props = { requiresConfirmation?: boolean; twoFactorEnabled?: boolean; -} +}; const breadcrumbs: BreadcrumbItem[] = [ { @@ -28,7 +27,7 @@ const breadcrumbs: BreadcrumbItem[] = [ export default function TwoFactor({ requiresConfirmation = false, twoFactorEnabled = false, -}: TwoFactorProps) { +}: Props) { const { qrCodeSvg, hasSetupData, diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx index 7af074a6e..dd58e2c26 100644 --- a/resources/js/pages/welcome.tsx +++ b/resources/js/pages/welcome.tsx @@ -1,7 +1,6 @@ -import { Head, Link, usePage } from '@inertiajs/react'; - import { dashboard, login, register } from '@/routes'; -import { type SharedData } from '@/types'; +import type { SharedData } from '@/types'; +import { Head, Link, usePage } from '@inertiajs/react'; export default function Welcome({ canRegister = true, diff --git a/resources/js/types/auth.ts b/resources/js/types/auth.ts new file mode 100644 index 000000000..5ffc370b0 --- /dev/null +++ b/resources/js/types/auth.ts @@ -0,0 +1,24 @@ +export type User = { + id: number; + name: string; + email: string; + avatar?: string; + email_verified_at: string | null; + two_factor_enabled?: boolean; + created_at: string; + updated_at: string; + [key: string]: unknown; +}; + +export type Auth = { + user: User; +}; + +export type TwoFactorSetupData = { + svg: string; + url: string; +}; + +export type TwoFactorSecretKey = { + secretKey: string; +}; diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts deleted file mode 100644 index 5c7bc2e1b..000000000 --- a/resources/js/types/index.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { InertiaLinkProps } from '@inertiajs/react'; -import { LucideIcon } from 'lucide-react'; - -export interface Auth { - user: User; -} - -export interface BreadcrumbItem { - title: string; - href: string; -} - -export interface NavGroup { - title: string; - items: NavItem[]; -} - -export interface NavItem { - title: string; - href: NonNullable; - icon?: LucideIcon | null; - isActive?: boolean; -} - -export interface SharedData { - name: string; - auth: Auth; - sidebarOpen: boolean; - [key: string]: unknown; -} - -export interface User { - id: number; - name: string; - email: string; - avatar?: string; - email_verified_at: string | null; - two_factor_enabled?: boolean; - created_at: string; - updated_at: string; - [key: string]: unknown; // This allows for additional properties... -} diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts new file mode 100644 index 000000000..93cefe73b --- /dev/null +++ b/resources/js/types/index.ts @@ -0,0 +1,12 @@ +export type * from './auth'; +export type * from './navigation'; +export type * from './ui'; + +import type { Auth } from './auth'; + +export type SharedData = { + name: string; + auth: Auth; + sidebarOpen: boolean; + [key: string]: unknown; +}; diff --git a/resources/js/types/navigation.ts b/resources/js/types/navigation.ts new file mode 100644 index 000000000..49954d7bd --- /dev/null +++ b/resources/js/types/navigation.ts @@ -0,0 +1,14 @@ +import type { InertiaLinkProps } from '@inertiajs/react'; +import type { LucideIcon } from 'lucide-react'; + +export type BreadcrumbItem = { + title: string; + href: string; +}; + +export type NavItem = { + title: string; + href: NonNullable; + icon?: LucideIcon | null; + isActive?: boolean; +}; diff --git a/resources/js/types/ui.ts b/resources/js/types/ui.ts new file mode 100644 index 000000000..53dcf56b1 --- /dev/null +++ b/resources/js/types/ui.ts @@ -0,0 +1,14 @@ +import type { ReactNode } from 'react'; +import type { BreadcrumbItem } from './navigation'; + +export type AppLayoutProps = { + children: ReactNode; + breadcrumbs?: BreadcrumbItem[]; +}; + +export type AuthLayoutProps = { + children?: ReactNode; + name?: string; + title?: string; + description?: string; +};