feat(api,web): c2 consulta de clientes — list + search + auth flow
prisma: modelo Client + migração 20260527225728_add_client + seed dev (10 clientes)
api: GET /clients (list, busca, filtro atividade/financeiro, paginação) + GET /clients/:id
rep vê carteira própria; supervisor/admin vê tudo; activityStatus calculado de lastOrderAt
@sar/api-interface: ClientSummarySchema, ClientDetailSchema, ClientListResponseSchema
web: ClientsPage (tabela AntD, busca, filtro), DevLogin (token dev), authStore, Bearer no apiFetch
oq-4 resolvida: creditLimit gerenciado no SAR
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
53
apps/web/src/components/dev/DevLogin.tsx
Normal file
53
apps/web/src/components/dev/DevLogin.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// Componente de login dev — visível apenas quando NODE_ENV !== 'production' e sem token.
|
||||
// Em produção o token vem do master-login real (fora do escopo do MVP).
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Alert, Button, Card, Flex, Space, Typography } from 'antd';
|
||||
import { apiFetch } from '../../lib/api-client';
|
||||
import { authStore } from '../../lib/auth-store';
|
||||
import { AuthTokenResponseSchema } from '@sar/api-interface';
|
||||
|
||||
export function DevLogin({ onLogin }: { onLogin: () => void }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function handleLogin() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const raw = await apiFetch('/api/v1/auth/dev/token', {
|
||||
method: 'POST',
|
||||
body: { userId: 'user-001', workspaceId: 'dev-workspace', role: 'rep' },
|
||||
});
|
||||
const { accessToken } = AuthTokenResponseSchema.parse(raw);
|
||||
authStore.set(accessToken);
|
||||
onLogin();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Erro ao obter token');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex justify="center" align="center" style={{ minHeight: '100vh' }}>
|
||||
<Card style={{ width: 360 }}>
|
||||
<Space direction="vertical" size={20} style={{ width: '100%' }}>
|
||||
<Typography.Title level={3} style={{ margin: 0 }}>
|
||||
SAR · Login Dev
|
||||
</Typography.Title>
|
||||
<Alert
|
||||
type="warning"
|
||||
message="Ambiente de desenvolvimento"
|
||||
description="Este login automático não existe em produção."
|
||||
showIcon
|
||||
/>
|
||||
{error && <Alert type="error" message={error} showIcon />}
|
||||
<Button type="primary" block loading={loading} onClick={() => void handleLogin()}>
|
||||
Entrar como Rafael (rep · user-001)
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user