/ src / components / InvalidConfigDialog.tsx
InvalidConfigDialog.tsx
  1  import React from 'react'
  2  import { Box, Newline, Text, useInput } from 'ink'
  3  import { getTheme } from '../utils/theme.js'
  4  import { Select } from '@inkjs/ui'
  5  import { render } from 'ink'
  6  import { writeFileSync } from 'fs'
  7  import { ConfigParseError } from '../utils/errors.js'
  8  import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD.js'
  9  interface InvalidConfigHandlerProps {
 10    error: ConfigParseError
 11  }
 12  
 13  interface InvalidConfigDialogProps {
 14    filePath: string
 15    errorDescription: string
 16    onExit: () => void
 17    onReset: () => void
 18  }
 19  
 20  /**
 21   * Dialog shown when the Claude config file contains invalid JSON
 22   */
 23  function InvalidConfigDialog({
 24    filePath,
 25    errorDescription,
 26    onExit,
 27    onReset,
 28  }: InvalidConfigDialogProps): React.ReactNode {
 29    const theme = getTheme()
 30  
 31    // Handle escape key
 32    useInput((_, key) => {
 33      if (key.escape) {
 34        onExit()
 35      }
 36    })
 37  
 38    const exitState = useExitOnCtrlCD(() => process.exit(0))
 39  
 40    // Handler for Select onChange
 41    const handleSelect = (value: string) => {
 42      if (value === 'exit') {
 43        onExit()
 44      } else {
 45        onReset()
 46      }
 47    }
 48  
 49    return (
 50      <>
 51        <Box
 52          flexDirection="column"
 53          borderColor={theme.error}
 54          borderStyle="round"
 55          padding={1}
 56          width={70}
 57          gap={1}
 58        >
 59          <Text bold>Configuration Error</Text>
 60  
 61          <Box flexDirection="column" gap={1}>
 62            <Text>
 63              The configuration file at <Text bold>{filePath}</Text> contains
 64              invalid JSON.
 65            </Text>
 66            <Text>{errorDescription}</Text>
 67          </Box>
 68  
 69          <Box flexDirection="column">
 70            <Text bold>Choose an option:</Text>
 71            <Select
 72              options={[
 73                { label: 'Exit and fix manually', value: 'exit' },
 74                { label: 'Reset with default configuration', value: 'reset' },
 75              ]}
 76              onChange={handleSelect}
 77            />
 78          </Box>
 79        </Box>
 80        {exitState.pending ? (
 81          <Text dimColor>Press {exitState.keyName} again to exit</Text>
 82        ) : (
 83          <Newline />
 84        )}
 85      </>
 86    )
 87  }
 88  
 89  export function showInvalidConfigDialog({
 90    error,
 91  }: InvalidConfigHandlerProps): Promise<void> {
 92    return new Promise(resolve => {
 93      render(
 94        <InvalidConfigDialog
 95          filePath={error.filePath}
 96          errorDescription={error.message}
 97          onExit={() => {
 98            resolve()
 99            process.exit(1)
100          }}
101          onReset={() => {
102            writeFileSync(
103              error.filePath,
104              JSON.stringify(error.defaultConfig, null, 2),
105            )
106            resolve()
107            process.exit(0)
108          }}
109        />,
110        { exitOnCtrlC: false },
111      )
112    })
113  }