implemented

This commit is contained in:
counterweight 2025-12-20 23:06:05 +01:00
parent a31bd8246c
commit d3638e2e69
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
18 changed files with 1643 additions and 120 deletions

View file

@ -6,35 +6,18 @@ import { api } from "../../api";
import { sharedStyles } from "../../styles/shared";
import { Header } from "../../components/Header";
import { useRequireAuth } from "../../hooks/useRequireAuth";
import { components } from "../../generated/api";
import constants from "../../../../shared/constants.json";
interface InviteRecord {
id: number;
identifier: string;
godfather_id: number;
godfather_email: string;
status: string;
used_by_id: number | null;
used_by_email: string | null;
created_at: string;
spent_at: string | null;
revoked_at: string | null;
}
const { READY, SPENT, REVOKED } = constants.inviteStatuses;
interface PaginatedResponse<T> {
records: T[];
total: number;
page: number;
per_page: number;
total_pages: number;
}
interface UserOption {
id: number;
email: string;
}
// Use generated types from OpenAPI schema
type InviteRecord = components["schemas"]["InviteResponse"];
type PaginatedInvites = components["schemas"]["PaginatedResponse_InviteResponse_"];
type UserOption = components["schemas"]["AdminUserResponse"];
export default function AdminInvitesPage() {
const [data, setData] = useState<PaginatedResponse<InviteRecord> | null>(null);
const [data, setData] = useState<PaginatedInvites | null>(null);
const [error, setError] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [statusFilter, setStatusFilter] = useState<string>("");
@ -63,7 +46,7 @@ export default function AdminInvitesPage() {
if (status) {
url += `&status=${status}`;
}
const data = await api.get<PaginatedResponse<InviteRecord>>(url);
const data = await api.get<PaginatedInvites>(url);
setData(data);
} catch (err) {
setData(null);
@ -117,11 +100,11 @@ export default function AdminInvitesPage() {
const getStatusBadgeStyle = (status: string) => {
switch (status) {
case "ready":
case READY:
return styles.statusReady;
case "spent":
case SPENT:
return styles.statusSpent;
case "revoked":
case REVOKED:
return styles.statusRevoked;
default:
return {};
@ -198,9 +181,9 @@ export default function AdminInvitesPage() {
style={styles.filterSelect}
>
<option value="">All statuses</option>
<option value="ready">Ready</option>
<option value="spent">Spent</option>
<option value="revoked">Revoked</option>
<option value={READY}>Ready</option>
<option value={SPENT}>Spent</option>
<option value={REVOKED}>Revoked</option>
</select>
<span style={styles.totalCount}>
{data?.total ?? 0} invites
@ -240,7 +223,7 @@ export default function AdminInvitesPage() {
</td>
<td style={styles.tdDate}>{formatDate(record.created_at)}</td>
<td style={styles.td}>
{record.status === "ready" && (
{record.status === READY && (
<button
onClick={() => handleRevoke(record.id)}
style={styles.revokeButton}