Skip to content
Merged
31 changes: 3 additions & 28 deletions .tsc-baseline.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
{
"generatedAt": "2026-05-29T16:46:02.467Z",
"totalErrors": 123,
"generatedAt": "2026-05-31T13:23:04.590Z",
"totalErrors": 62,
"counts": {
"src/components/mockup/approval/OffscreenLayoutCapture.tsx": {
"TS2345": 1
},
"src/components/search/AdvancedSearch.tsx": {
"TS2345": 1
},
"src/hooks/admin/useAllowedIPs.ts": {
"TS2322": 5,
"TS2345": 3,
"TS2352": 1,
"TS2769": 4
},
"src/hooks/admin/useDeviceDetection.ts": {
"TS2353": 1
},
Expand All @@ -24,17 +18,7 @@
"TS2353": 1
},
"src/hooks/auth/useAccessSecurity.ts": {
"TS2322": 2,
"TS2345": 5,
"TS2352": 3,
"TS2353": 1,
"TS2769": 9
},
"src/hooks/intelligence/useSalesGoals.ts": {
"TS2322": 10,
"TS2345": 5,
"TS2353": 1,
"TS2769": 6
"TS2345": 1
},
"src/hooks/mockup/mockupGenerationService.ts": {
"TS2322": 1
Expand All @@ -57,11 +41,6 @@
"TS2589": 4,
"TS2769": 5
},
"src/hooks/simulation/useSimulation.ts": {
"TS2345": 1,
"TS2589": 1,
"TS2769": 3
},
"src/hooks/simulation/useTechniquePricing.ts": {
"TS2339": 16,
"TS2345": 1,
Expand Down Expand Up @@ -89,10 +68,6 @@
},
"src/pages/products/seller-carts/CartSidebar.tsx": {
"TS2322": 1
},
"src/pages/quotes/QuotesKanbanPage.tsx": {
"TS2345": 1,
"TS2769": 1
}
}
}
51 changes: 20 additions & 31 deletions src/components/admin/access-security/BlockedLogsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,14 @@ import { ptBR } from 'date-fns/locale';
interface BlockedLog {
id: string;
created_at: string;
email: string | null;
ip_address: string;
city: string | null;
state: string | null;
country: string | null;
block_reason: string;
user_email: string | null;
ip_address: unknown;
error_message: string | null;
operation: string;
table_name: string;
user_agent: string | null;
}

const BLOCK_REASON_LABELS: Record<string, string> = {
ip_not_whitelisted: 'IP não autorizado',
city_not_whitelisted: 'Cidade não autorizada',
too_many_attempts: 'Muitas tentativas',
};

interface BlockedLogsTabProps {
logs: BlockedLog[];
}
Expand All @@ -37,26 +31,26 @@ export function BlockedLogsTab({ logs }: BlockedLogsTabProps) {
return (
<Card className="border-border/50">
<CardHeader>
<CardTitle className="text-base">Últimos Acessos Bloqueados</CardTitle>
<CardTitle className="text-base">Últimos Acessos Negados (RLS)</CardTitle>
<CardDescription>
Registro das últimas 50 tentativas de acesso bloqueadas pelo sistema
Registro das últimas 50 negações de acesso pelo Row Level Security
</CardDescription>
</CardHeader>
<CardContent>
{logs.length === 0 ? (
<div className="py-8 text-center text-muted-foreground">
<ShieldAlert className="mx-auto mb-2 h-8 w-8 opacity-40" />
Nenhum acesso bloqueado registrado
Nenhum acesso negado registrado
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Data/Hora</TableHead>
<TableHead>Email</TableHead>
<TableHead>IP</TableHead>
<TableHead>Localização</TableHead>
<TableHead>Motivo</TableHead>
<TableHead>Usuário</TableHead>
<TableHead>Operação</TableHead>
<TableHead>Tabela</TableHead>
<TableHead>Erro</TableHead>
</TableRow>
</TableHeader>
<TableBody>
Expand All @@ -65,21 +59,16 @@ export function BlockedLogsTab({ logs }: BlockedLogsTabProps) {
<TableCell className="whitespace-nowrap text-xs text-muted-foreground">
{format(new Date(log.created_at), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })}
</TableCell>
<TableCell className="text-sm">{log.email || '—'}</TableCell>
<TableCell className="font-mono text-xs">{log.ip_address}</TableCell>
<TableCell className="text-sm">
{log.city
? `${log.city}${log.state ? `, ${log.state}` : ''}${log.country ? ` (${log.country})` : ''}`
: '—'}
</TableCell>
<TableCell className="text-sm">{log.user_email || '—'}</TableCell>
<TableCell>
<Badge
variant={log.block_reason === 'too_many_attempts' ? 'destructive' : 'outline'}
className="text-xs"
>
{BLOCK_REASON_LABELS[log.block_reason] || log.block_reason}
<Badge variant="outline" className="font-mono text-xs">
{log.operation}
</Badge>
</TableCell>
<TableCell className="font-mono text-xs">{log.table_name}</TableCell>
<TableCell className="max-w-48 truncate text-xs text-muted-foreground">
{log.error_message || '—'}
</TableCell>
</TableRow>
))}
</TableBody>
Expand Down
87 changes: 45 additions & 42 deletions src/components/admin/access-security/CityWhitelistTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,104 +27,107 @@ import { Globe, Loader2, Plus, Trash2 } from 'lucide-react';
import { format } from 'date-fns';
import { ptBR } from 'date-fns/locale';

interface CityEntry {
interface CountryEntry {
id: string;
city_name: string;
state: string | null;
country_code: string;
is_active: boolean;
created_at: string;
country_name: string;
is_active: boolean | null;
created_at: string | null;
}

interface CityWhitelistTabProps {
cities: CityEntry[];
onAdd: (city: string, state?: string) => Promise<boolean>;
cities: CountryEntry[];
onAdd: (country_code: string, state?: string) => Promise<boolean>;
onRemove: (id: string) => void;
onToggle: (id: string, active: boolean) => void;
}

export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhitelistTabProps) {
const [newCity, setNewCity] = useState('');
const [newState, setNewState] = useState('');
const [newCode, setNewCode] = useState('');
const [newName, setNewName] = useState('');
const [adding, setAdding] = useState(false);

const handleAdd = async () => {
if (!newCity.trim()) return;
if (!newCode.trim() || !newName.trim()) return;
setAdding(true);
const ok = await onAdd(newCity.trim(), newState.trim() || undefined);
const ok = await onAdd(newCode.trim().toUpperCase(), newName.trim());
if (ok) {
setNewCity('');
setNewState('');
setNewCode('');
setNewName('');
}
setAdding(false);
};

return (
<Card className="border-border/50">
<CardHeader>
<CardTitle className="text-base">Cidades na Whitelist</CardTitle>
<CardTitle className="text-base">Países na Whitelist</CardTitle>
<CardDescription>
Adicione cidades de onde é permitido acessar o sistema. A localização é detectada pelo IP
Adicione países de onde é permitido acessar o sistema. A localização é detectada pelo IP
do usuário.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-end gap-2">
<div className="flex-1 space-y-1">
<Label className="text-xs">Cidade</Label>
<div className="w-28 space-y-1">
<Label className="text-xs">Código (ISO)</Label>
<Input
placeholder="Ex: São Paulo"
value={newCity}
onChange={(e) => setNewCity(e.target.value)}
placeholder="BR"
value={newCode}
onChange={(e) => setNewCode(e.target.value)}
maxLength={2}
onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
/>
</div>
<div className="w-24 space-y-1">
<Label className="text-xs">Estado</Label>
<div className="flex-1 space-y-1">
<Label className="text-xs">Nome do País</Label>
<Input
placeholder="SP"
value={newState}
onChange={(e) => setNewState(e.target.value)}
maxLength={2}
placeholder="Ex: Brasil"
value={newName}
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
/>
</div>
<Button onClick={handleAdd} disabled={adding || !newCity.trim()} className="gap-1">
<Button
onClick={handleAdd}
disabled={adding || !newCode.trim() || !newName.trim()}
className="gap-1"
>
{adding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
Adicionar
</Button>
</div>
{cities.length === 0 ? (
<div className="py-8 text-center text-muted-foreground">
<Globe className="mx-auto mb-2 h-8 w-8 opacity-40" />
Nenhuma cidade cadastrada
Nenhum país cadastrado
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Cidade</TableHead>
<TableHead>Estado</TableHead>
<TableHead>Código</TableHead>
<TableHead>País</TableHead>
<TableHead>Status</TableHead>
<TableHead>Adicionado em</TableHead>
<TableHead className="text-right">Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{cities.map((city) => (
<TableRow key={city.id}>
<TableCell className="font-medium">{city.city_name}</TableCell>
<TableCell>{city.state || '—'}</TableCell>
<TableCell>{city.country_code}</TableCell>
{cities.map((country) => (
<TableRow key={country.id}>
<TableCell className="font-mono font-medium">{country.country_code}</TableCell>
<TableCell>{country.country_name}</TableCell>
<TableCell>
<Switch
checked={city.is_active}
onCheckedChange={(checked) => onToggle(city.id, checked)}
checked={country.is_active ?? true}
onCheckedChange={(checked) => onToggle(country.id, checked)}
/>
</TableCell>
<TableCell className="text-xs text-muted-foreground">
{format(new Date(city.created_at), 'dd/MM/yyyy HH:mm', { locale: ptBR })}
{country.created_at
? format(new Date(country.created_at), 'dd/MM/yyyy HH:mm', { locale: ptBR })
: '—'}
</TableCell>
<TableCell className="text-right">
<AlertDialog>
Expand All @@ -140,16 +143,16 @@ export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhit
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Remover cidade?</AlertDialogTitle>
<AlertDialogTitle>Remover país?</AlertDialogTitle>
<AlertDialogDescription>
<span className="font-bold">{city.city_name}</span> será removida da
whitelist.
<span className="font-bold">{country.country_name}</span> será removido
da whitelist.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={() => onRemove(city.id)}
onClick={() => onRemove(country.id)}
className="bg-destructive text-destructive-foreground"
>
Remover
Expand Down
4 changes: 2 additions & 2 deletions src/components/admin/access-security/IpWhitelistTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { ptBR } from 'date-fns/locale';
interface IpEntry {
id: string;
ip_address: string;
label: string | null;
reason: string | null;
is_active: boolean;
created_at: string;
}
Expand Down Expand Up @@ -112,7 +112,7 @@ export function IpWhitelistTab({ ips, onAdd, onRemove, onToggle }: IpWhitelistTa
{ips.map((ip) => (
<TableRow key={ip.id}>
<TableCell className="font-mono text-sm">{ip.ip_address}</TableCell>
<TableCell>{ip.label || '—'}</TableCell>
<TableCell>{ip.reason || '—'}</TableCell>
<TableCell>
<Switch
checked={ip.is_active}
Expand Down
29 changes: 13 additions & 16 deletions src/components/ai/AIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,23 +193,20 @@ export function AIChat({
const token = sessionData?.session?.access_token;
if (!token) throw new Error('Usuário não autenticado');

const response = await fetch(
`${SUPABASE_URL}/functions/v1/expert-chat`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
apikey: SUPABASE_PUBLISHABLE_KEY,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
messages: nextMessages.map((m) => ({ role: m.role, content: m.content })),
systemPrompt,
contextId,
}),
signal: controller.signal,
const response = await fetch(`${SUPABASE_URL}/functions/v1/expert-chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
apikey: SUPABASE_PUBLISHABLE_KEY,
Authorization: `Bearer ${token}`,
},
);
body: JSON.stringify({
messages: nextMessages.map((m) => ({ role: m.role, content: m.content })),
systemPrompt,
contextId,
}),
signal: controller.signal,
});

if (!response.ok) {
if (response.status === 429)
Expand Down
Loading
Loading