File size: 5,870 Bytes
ce8f04a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useState } from 'react';
import { Key, Loader2, Plus, Trash2, Copy } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import toast from 'react-hot-toast';
import { createDeveloperApiKey, listDeveloperApiKeys, revokeDeveloperApiKey } from '../../api/client';

export const DeveloperApiKeysPanel: React.FC = () => {
    const [label, setLabel] = useState('');
    const [revealedKey, setRevealedKey] = useState<string | null>(null);
    const queryClient = useQueryClient();

    const { data, isLoading } = useQuery({
        queryKey: ['developer-api-keys'],
        queryFn: listDeveloperApiKeys,
    });

    const createMutation = useMutation({
        mutationFn: () => createDeveloperApiKey(label.trim()),
        onSuccess: (res) => {
            setRevealedKey(res.api_key);
            setLabel('');
            queryClient.invalidateQueries({ queryKey: ['developer-api-keys'] });
            toast.success('Klucz API utworzony — skopiuj go teraz (nie będzie widoczny ponownie).');
        },
        onError: () => toast.error('Nie udało się utworzyć klucza.'),
    });

    const revokeMutation = useMutation({
        mutationFn: revokeDeveloperApiKey,
        onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: ['developer-api-keys'] });
            toast.success('Klucz unieważniony.');
        },
        onError: () => toast.error('Nie udało się unieważnić klucza.'),
    });

    const keys = data?.keys || [];

    return (
        <div style={{ padding: '1.5rem 0' }}>
            <h3 style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
                <Key size={18} /> Public API v2 — klucze deweloperskie
            </h3>
            <p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', marginBottom: '1.5rem' }}>
                Klucze umożliwiają dostęp do <code>/api/v2/public/*</code> (katalog naborów, health). Specyfikacja: <code>/api/v2/public/openapi.json</code>
            </p>

            {revealedKey && (
                <div style={{ background: 'rgba(16,185,129,0.1)', border: '1px solid #10b981', borderRadius: '8px', padding: '1rem', marginBottom: '1rem' }}>
                    <div style={{ fontSize: '0.85rem', color: '#10b981', marginBottom: '0.5rem', fontWeight: 600 }}>Nowy klucz (skopiuj teraz):</div>
                    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
                        <code style={{ wordBreak: 'break-all', flex: 1 }}>{revealedKey}</code>
                        <button
                            type="button"
                            onClick={() => { navigator.clipboard.writeText(revealedKey); toast.success('Skopiowano'); }}
                            style={{ background: 'transparent', border: '1px solid #10b981', color: '#10b981', padding: '0.35rem 0.6rem', borderRadius: '6px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.25rem' }}
                        >
                            <Copy size={14} /> Kopiuj
                        </button>
                    </div>
                </div>
            )}

            <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem', flexWrap: 'wrap' }}>
                <input
                    placeholder="Etykieta klucza (np. integracja CRM)"
                    value={label}
                    onChange={(e) => setLabel(e.target.value)}
                    style={{ flex: 1, minWidth: '200px', padding: '0.5rem 0.75rem', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'rgba(0,0,0,0.2)', color: '#fff' }}
                />
                <button
                    className="btn btn-primary"
                    disabled={!label.trim() || createMutation.isPending}
                    onClick={() => createMutation.mutate()}
                    style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}
                >
                    {createMutation.isPending ? <Loader2 size={16} className="animate-spin" /> : <Plus size={16} />}
                    Utwórz klucz
                </button>
            </div>

            {isLoading ? (
                <Loader2 className="animate-spin" size={24} />
            ) : keys.length === 0 ? (
                <p style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>Brak aktywnych kluczy.</p>
            ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    {keys.map((k) => (
                        <div key={k.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0.75rem 1rem', background: 'rgba(255,255,255,0.03)', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.08)' }}>
                            <div>
                                <div style={{ fontWeight: 600 }}>{k.label}</div>
                                <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{k.key_prefix}… · {new Date(k.created_at).toLocaleDateString('pl-PL')}</div>
                            </div>
                            <button
                                type="button"
                                onClick={() => revokeMutation.mutate(k.id)}
                                disabled={revokeMutation.isPending}
                                style={{ background: 'transparent', border: 'none', color: '#ef4444', cursor: 'pointer' }}
                                title="Unieważnij"
                            >
                                <Trash2 size={18} />
                            </button>
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
};

export default DeveloperApiKeysPanel;