feat(dashboard): add transactions and users management pages with CRUD functionality
feat(auth): implement login page and authentication middleware feat(sidebar): create sidebar component with user info and navigation links feat(api): establish API client for interacting with backend services
This commit is contained in:
175
apps/web/lib/api.ts
Normal file
175
apps/web/lib/api.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
const CSMS_URL = process.env.NEXT_PUBLIC_CSMS_URL ?? "http://localhost:3001";
|
||||
|
||||
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${CSMS_URL}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...init?.headers,
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => res.statusText);
|
||||
throw new Error(`API ${path} failed (${res.status}): ${text}`);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export type Stats = {
|
||||
totalChargePoints: number;
|
||||
onlineChargePoints: number;
|
||||
activeTransactions: number;
|
||||
totalIdTags: number;
|
||||
todayEnergyWh: number;
|
||||
};
|
||||
|
||||
export type ConnectorSummary = {
|
||||
id: number;
|
||||
connectorId: number;
|
||||
status: string;
|
||||
lastStatusAt: string | null;
|
||||
};
|
||||
|
||||
export type ChargePoint = {
|
||||
id: number;
|
||||
chargePointIdentifier: string;
|
||||
chargePointVendor: string | null;
|
||||
chargePointModel: string | null;
|
||||
registrationStatus: string;
|
||||
lastHeartbeatAt: string | null;
|
||||
lastBootNotificationAt: string | null;
|
||||
feePerKwh: number;
|
||||
connectors: ConnectorSummary[];
|
||||
};
|
||||
|
||||
export type Transaction = {
|
||||
id: number;
|
||||
chargePointIdentifier: string | null;
|
||||
connectorNumber: number | null;
|
||||
idTag: string;
|
||||
idTagStatus: string | null;
|
||||
startTimestamp: string;
|
||||
stopTimestamp: string | null;
|
||||
startMeterValue: number | null;
|
||||
stopMeterValue: number | null;
|
||||
energyWh: number | null;
|
||||
stopIdTag: string | null;
|
||||
stopReason: string | null;
|
||||
chargeAmount: number | null;
|
||||
};
|
||||
|
||||
export type IdTag = {
|
||||
idTag: string;
|
||||
status: string;
|
||||
expiryDate: string | null;
|
||||
parentIdTag: string | null;
|
||||
userId: string | null;
|
||||
balance: number;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type UserRow = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
username: string | null;
|
||||
role: string | null;
|
||||
banned: boolean | null;
|
||||
banReason: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type PaginatedTransactions = {
|
||||
data: Transaction[];
|
||||
total: number;
|
||||
page: number;
|
||||
totalPages: number;
|
||||
};
|
||||
|
||||
// ── API functions ──────────────────────────────────────────────────────────
|
||||
|
||||
export const api = {
|
||||
stats: {
|
||||
get: () => apiFetch<Stats>("/api/stats"),
|
||||
},
|
||||
chargePoints: {
|
||||
list: () => apiFetch<ChargePoint[]>("/api/charge-points"),
|
||||
get: (id: number) => apiFetch<ChargePoint>(`/api/charge-points/${id}`),
|
||||
update: (id: string, data: { feePerKwh: number }) =>
|
||||
apiFetch<{ feePerKwh: number }>(`/api/charge-points/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
delete: (id: string) =>
|
||||
apiFetch<{ success: true }>(`/api/charge-points/${id}`, { method: "DELETE" }),
|
||||
},
|
||||
transactions: {
|
||||
list: (params?: { page?: number; limit?: number; status?: "active" | "completed" }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params?.page) q.set("page", String(params.page));
|
||||
if (params?.limit) q.set("limit", String(params.limit));
|
||||
if (params?.status) q.set("status", params.status);
|
||||
const qs = q.toString();
|
||||
return apiFetch<PaginatedTransactions>(`/api/transactions${qs ? "?" + qs : ""}`);
|
||||
},
|
||||
get: (id: number) => apiFetch<Transaction>(`/api/transactions/${id}`),
|
||||
stop: (id: number) =>
|
||||
apiFetch<Transaction & { online: boolean }>(`/api/transactions/${id}/stop`, {
|
||||
method: "POST",
|
||||
}),
|
||||
delete: (id: number) =>
|
||||
apiFetch<{ success: true }>(`/api/transactions/${id}`, { method: "DELETE" }),
|
||||
},
|
||||
idTags: {
|
||||
list: () => apiFetch<IdTag[]>("/api/id-tags"),
|
||||
get: (idTag: string) => apiFetch<IdTag>(`/api/id-tags/${idTag}`),
|
||||
create: (data: {
|
||||
idTag: string;
|
||||
status?: string;
|
||||
expiryDate?: string;
|
||||
parentIdTag?: string;
|
||||
userId?: string | null;
|
||||
balance?: number;
|
||||
}) => apiFetch<IdTag>("/api/id-tags", { method: "POST", body: JSON.stringify(data) }),
|
||||
update: (
|
||||
idTag: string,
|
||||
data: {
|
||||
status?: string;
|
||||
expiryDate?: string | null;
|
||||
parentIdTag?: string | null;
|
||||
userId?: string | null;
|
||||
balance?: number;
|
||||
},
|
||||
) => apiFetch<IdTag>(`/api/id-tags/${idTag}`, { method: "PATCH", body: JSON.stringify(data) }),
|
||||
delete: (idTag: string) =>
|
||||
apiFetch<{ success: true }>(`/api/id-tags/${idTag}`, { method: "DELETE" }),
|
||||
},
|
||||
users: {
|
||||
list: () => apiFetch<UserRow[]>("/api/users"),
|
||||
create: (data: {
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
username?: string;
|
||||
role?: string;
|
||||
}) =>
|
||||
apiFetch<{ user: UserRow }>("/api/auth/admin/create-user", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
update: (
|
||||
id: string,
|
||||
data: {
|
||||
name?: string;
|
||||
username?: string | null;
|
||||
role?: string;
|
||||
banned?: boolean;
|
||||
banReason?: string | null;
|
||||
},
|
||||
) => apiFetch<UserRow>(`/api/users/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
|
||||
},
|
||||
};
|
||||
9
apps/web/lib/auth-client.ts
Normal file
9
apps/web/lib/auth-client.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { adminClient, usernameClient } from "better-auth/client/plugins";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: process.env.NEXT_PUBLIC_CSMS_URL ?? "http://localhost:3001",
|
||||
plugins: [usernameClient(), adminClient()],
|
||||
});
|
||||
|
||||
export const { signIn, signOut, signUp, useSession } = authClient;
|
||||
Reference in New Issue
Block a user