feat(security-appeals): Update AppealsPage component with status indicators, action buttons, and forms for security appeal management

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-25 23:56:36 -07:00
parent e8421add08
commit 403a1c05a5

View file

@ -20,63 +20,23 @@ import type { Appeal, AppealStats, PaginatedAppeals, ListAppealsQuery } from '@/
import { fetchAppeals, fetchAppealStats, reviewAppeal } from '@/api'
import { AppealStatus } from '@/types'
const PageContainer = styled.div`
display: flex;
flex-direction: column;
gap: 1.5rem;
`
const HeaderSection = styled.div`
margin-bottom: 1rem;
`
const CardContent = styled.div`
padding: 1rem;
`
const StatCardContent = styled.div`
text-align: center;
padding: 1rem;
`
const StatValue = styled.div<{ $variant?: 'default' | 'warning' | 'danger' | 'success' }>`
font-size: 2rem;
font-weight: 700;
color: ${({ $variant }): string =>
$variant === 'danger'
? '#ef4444'
: $variant === 'warning'
? '#f59e0b'
: $variant === 'success'
? '#22c55e'
: 'var(--text-primary)'};
`
const StatLabel = styled.div`
font-size: 0.875rem;
color: var(--text-secondary);
margin-top: 0.25rem;
`
const FiltersRow = styled.div`
display: flex;
gap: 1rem;
align-items: flex-end;
`
const FilterGroup = styled.div`
display: flex;
flex-direction: column;
gap: 0.25rem;
`
const FilterLabel = styled.label`
font-size: 0.75rem;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
`
import {
PageContainer,
HeaderSection,
StatCardContent,
StatValue,
StatLabel,
CardHeader,
CardContent,
FiltersRow,
FilterGroup,
FilterLabel,
LoadingPlaceholder,
TableContainer,
AdminTable,
AdminTh,
AdminTd,
} from '@/components/admin-pages/SharedPageComponents'
const StatusBadge = styled(Badge)<{ $status: AppealStatus }>`
background: ${({ $status }): string =>
@ -88,31 +48,6 @@ const StatusBadge = styled(Badge)<{ $status: AppealStatus }>`
color: white;
`
const TableContainer = styled.div`
overflow-x: auto;
`
const Table = styled.table`
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
`
const Th = styled.th`
text-align: left;
padding: 0.75rem 1rem;
font-weight: 600;
color: var(--text-secondary);
border-bottom: 1px solid var(--border-color);
background: var(--bg-secondary);
`
const Td = styled.td`
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
`
const PaginationContainer = styled.div`
display: flex;
justify-content: space-between;
@ -179,34 +114,12 @@ const ModalActions = styled.div`
border-top: 1px solid var(--border-color);
`
const CardHeader = styled.div`
padding: 1rem;
border-bottom: 1px solid var(--border-color);
`
const CardTitle = styled.h3`
font-size: 1rem;
font-weight: 600;
margin: 0;
`
const LoadingPlaceholder = styled.div`
height: 2rem;
background: var(--bg-tertiary);
border-radius: 4px;
animation: pulse 1.5s infinite;
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
`
const STATUS_OPTIONS = [
{ value: '', label: 'All Statuses' },
{ value: AppealStatus.PENDING, label: 'Pending' },
@ -396,32 +309,32 @@ export const AppealsPage = (): ReactElement => {
) : appeals.length === 0 ? (
<EmptyState>No appeals found</EmptyState>
) : (
<Table>
<AdminTable>
<thead>
<tr>
<Th>Submitted</Th>
<Th>Email</Th>
<Th>Status</Th>
<Th>Detected Org</Th>
<Th>Country</Th>
<Th>Block Type</Th>
<Th>Actions</Th>
<AdminTh>Submitted</AdminTh>
<AdminTh>Email</AdminTh>
<AdminTh>Status</AdminTh>
<AdminTh>Detected Org</AdminTh>
<AdminTh>Country</AdminTh>
<AdminTh>Block Type</AdminTh>
<AdminTh>Actions</AdminTh>
</tr>
</thead>
<tbody>
{appeals.map((appeal) => (
<tr key={appeal.id}>
<Td title={format(new Date(appeal.createdAt), 'PPpp')}>
<AdminTd title={format(new Date(appeal.createdAt), 'PPpp')}>
{formatDistanceToNow(new Date(appeal.createdAt), { addSuffix: true })}
</Td>
<Td>{appeal.email}</Td>
<Td>
</AdminTd>
<AdminTd>{appeal.email}</AdminTd>
<AdminTd>
<StatusBadge $status={appeal.status}>{appeal.status}</StatusBadge>
</Td>
<Td>{appeal.detectedOrg || '-'}</Td>
<Td>{appeal.detectedCountry || '-'}</Td>
<Td>{appeal.responseTier || '-'}</Td>
<Td>
</AdminTd>
<AdminTd>{appeal.detectedOrg || '-'}</AdminTd>
<AdminTd>{appeal.detectedCountry || '-'}</AdminTd>
<AdminTd>{appeal.responseTier || '-'}</AdminTd>
<AdminTd>
{appeal.status === AppealStatus.PENDING ? (
<Button size="sm" variant="secondary" onClick={(): void => handleReviewClick(appeal)}>
Review
@ -433,11 +346,11 @@ export const AppealsPage = (): ReactElement => {
: '-'}
</span>
)}
</Td>
</AdminTd>
</tr>
))}
</tbody>
</Table>
</AdminTable>
)}
</TableContainer>
<CardContent>