# All files / src/components/settings OpenAISettingsCard.tsx

96.42% Statements 108/112
87.5% Branches 14/16
100% Functions 5/5
96.42% Lines 108/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    1131x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    70x
    70x
    70x
    70x
    70x
    1x
    1x
    1x
    1x
    1x

    1x
    70x
    70x
    1x
    1x
    1x
    1x
    1x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
    70x
     'use client';

    import { useState } from 'react';
    import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
    import { Input } from '@/components/ui/input';
    import { Button } from '@/components/ui/button';
    import { Label } from '@/components/ui/label';
    import { Eye, EyeClosed, Key } from '@phosphor-icons/react';

    export function OpenAISettingsCard() {
      const [apiKey, setApiKey] = useState(() => (typeof window !== 'undefined' ? localStorage.getItem('openai_api_key') || '' : ''));
      const [showKey, setShowKey] = useState(false);
      const [saved, setSaved] = useState(false);

      const handleSave = () => {
        if (apiKey.trim()) {
          localStorage.setItem('openai_api_key', apiKey.trim());
          setSaved(true);
          setTimeout(() => setSaved(false), 2000);
        } else {
          localStorage.removeItem('openai_api_key');
          setSaved(true);
          setTimeout(() => setSaved(false), 2000);
        }
      };

      const handleClear = () => {
        setApiKey('');
        localStorage.removeItem('openai_api_key');
        setSaved(true);
        setTimeout(() => setSaved(false), 2000);
      };

      return (
        <Card data-testid="openai-settings-card">
          <CardHeader>
            <div className="flex items-center gap-2">
              <Key className="h-5 w-5 text-primary" weight="duotone" aria-hidden="true" />
              <CardTitle>OpenAI API Settings</CardTitle>
            </div>
            <CardDescription>
              Configure your OpenAI API key for AI-powered error analysis. Your key is stored locally in your browser.
            </CardDescription>
          </CardHeader>
          <CardContent className="space-y-4">
            <div className="space-y-2">
              <Label htmlFor="openai-key">OpenAI API Key</Label>
              <div className="flex gap-2">
                <div className="relative flex-1">
                  <Input
                    id="openai-key"
                    type={showKey ? 'text' : 'password'}
                    value={apiKey}
                    onChange={(e) => setApiKey(e.target.value)}
                    placeholder="sk-..."
                    className="pr-10"
                    data-testid="openai-api-key-input"
                    aria-label="OpenAI API key"
                  />
                  <button
                    type="button"
                    onClick={() => setShowKey(!showKey)}
                    className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
                    data-testid="toggle-api-key-visibility"
                    aria-label={showKey ? 'Hide API key' : 'Show API key'}
                    aria-pressed={showKey}
                  >
                    {showKey ? <EyeClosed size={18} aria-hidden="true" /> : <Eye size={18} aria-hidden="true" />}
                  </button>
                </div>
              </div>
              <p className="text-xs text-muted-foreground">
                Get your API key from{' '}
                <a
                  href="https://platform.openai.com/api-keys"
                  target="_blank"
                  rel="noopener noreferrer"
                  className="text-primary hover:underline"
                >
                  OpenAI Platform
                </a>
              </p>
            </div>

            <div className="flex gap-2">
              <Button
                onClick={handleSave}
                disabled={!apiKey.trim()}
                data-testid="save-api-key-btn"
              >
                {saved ? 'Saved!' : 'Save API Key'}
              </Button>
              {apiKey && (
                <Button
                  onClick={handleClear}
                  variant="outline"
                  data-testid="clear-api-key-btn"
                >
                  Clear
                </Button>
              )}
            </div>

            {apiKey && (
              <div className="text-sm text-muted-foreground bg-muted/50 p-3 rounded-md">
                ✓ API key is configured. Error analysis will use OpenAI GPT-4o-mini.
              </div>
            )}
          </CardContent>
        </Card>
      );
    }