fuck if i know

This commit is contained in:
2025-12-04 15:06:57 -05:00
parent fb3c23fa46
commit cb16698940
76 changed files with 1197 additions and 603 deletions
+26
View File
@@ -0,0 +1,26 @@
import { useAuth } from "@/context/AuthContext";
export function useApi() {
const { token } = useAuth();
async function get(path: string) {
return fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}${path}`, {
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
}
async function post(path: string, body: any) {
return fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token ? `Bearer ${token}` : "",
},
body: JSON.stringify(body),
});
}
return { get, post };
}
+134
View File
@@ -0,0 +1,134 @@
// lib/api/adminCategoryMappings.ts
const API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
export interface AdminCategory {
id: number;
slug: string;
name: string;
description?: string | null;
groupName?: string | null;
sortOrder?: number | null;
}
export interface AdminPartRoleMapping {
id: number;
platform: string;
partRole: string;
categorySlug: string;
groupName?: string | null;
notes?: string | null;
}
export interface CreatePartRoleMappingRequest {
platform: string;
partRole: string;
categorySlug: string;
notes?: string;
}
export interface UpdatePartRoleMappingRequest {
platform?: string;
partRole?: string;
categorySlug?: string;
notes?: string;
}
async function apiFetch<T>(
path: string,
options: {
token?: string;
method?: string;
body?: any;
} = {}
): Promise<T> {
const { token, method = "GET", body } = options;
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const res = await fetch(`${API_BASE}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(
`API error ${res.status}: ${res.statusText}${text ? ` ${text}` : ""}`
);
}
return (await res.json()) as T;
}
export async function fetchAdminCategories(token: string) {
return apiFetch<AdminCategory[]>("/api/admin/categories", { token });
}
// ---------------- Part-role mappings ----------------
const PART_ROLE_BASE = "/api/admin/part-role-mappings";
/**
* Fetch part-role/category mappings scoped by platform.
* Backend should expose: GET /api/admin/part-role-mappings?platform=AR-15
*/
export async function fetchPartRoleMappings(
token: string,
platform = "AR-15"
) {
const params = new URLSearchParams({ platform });
return apiFetch<AdminPartRoleMapping[]>(
`${PART_ROLE_BASE}?${params.toString()}`,
{ token }
);
}
/**
* Create a new part-role → category mapping
* POST /api/admin/part-role-mappings
*/
export async function createPartRoleMapping(
token: string,
payload: CreatePartRoleMappingRequest
) {
return apiFetch<AdminPartRoleMapping>(PART_ROLE_BASE, {
token,
method: "POST",
body: payload,
});
}
/**
* Update an existing part-role mapping
* PUT /api/admin/part-role-mappings/{id}
*/
export async function updatePartRoleMapping(
token: string,
id: number,
payload: UpdatePartRoleMappingRequest
) {
return apiFetch<AdminPartRoleMapping>(`${PART_ROLE_BASE}/${id}`, {
token,
method: "PUT",
body: payload,
});
}
/**
* Delete a part-role mapping
* DELETE /api/admin/part-role-mappings/{id}
*/
export async function deletePartRoleMapping(token: string, id: number) {
await apiFetch<void>(`${PART_ROLE_BASE}/${id}`, {
token,
method: "DELETE",
});
}
+60
View File
@@ -0,0 +1,60 @@
const API_BASE =
process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080";
export interface AdminMerchantCategoryMapping {
id: number;
merchantId: number;
merchantName: string;
rawCategoryPath: string;
partCategorySlug: string | null;
}
export async function fetchMerchantCategoryMappings(
token: string,
filters?: { merchantId?: number }
): Promise<AdminMerchantCategoryMapping[]> {
const params = new URLSearchParams();
if (filters?.merchantId) {
params.append("merchantId", String(filters.merchantId));
}
const res = await fetch(
`${API_BASE}/api/admin/category-mappings?${params.toString()}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
cache: "no-store",
}
);
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.json();
}
export async function updateMerchantCategoryMapping(
token: string,
id: number,
payload: { partCategorySlug: string | null }
): Promise<AdminMerchantCategoryMapping> {
const res = await fetch(`${API_BASE}/api/admin/category-mappings/${id}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`API error ${res.status}: ${text}`);
}
return res.json();
}
+116
View File
@@ -0,0 +1,116 @@
// lib/auth-client.ts
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
export type AuthUser = {
id: number;
email: string;
displayName?: string | null;
role: string;
};
export type AuthPayload = {
accessToken: string;
refreshToken?: string;
userId: number;
email: string;
displayName?: string | null;
role: string;
};
export type RegisterInput = {
email: string;
password: string;
displayName?: string;
};
export type LoginInput = {
email: string;
password: string;
};
const AUTH_STORAGE_KEY = "armory-auth";
export function storeAuth(payload: AuthPayload) {
if (typeof window === "undefined") return;
const user: AuthUser = {
id: payload.userId,
email: payload.email,
displayName: payload.displayName,
role: payload.role,
};
const toStore = {
user,
accessToken: payload.accessToken,
refreshToken: payload.refreshToken ?? null,
};
window.localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(toStore));
}
export function loadStoredAuth():
| { user: AuthUser; accessToken: string; refreshToken: string | null }
| null {
if (typeof window === "undefined") return null;
try {
const raw = window.localStorage.getItem(AUTH_STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!parsed.user || !parsed.accessToken) return null;
return {
user: parsed.user as AuthUser,
accessToken: parsed.accessToken as string,
refreshToken: parsed.refreshToken ?? null,
};
} catch {
return null;
}
}
export function clearStoredAuth() {
if (typeof window === "undefined") return;
window.localStorage.removeItem(AUTH_STORAGE_KEY);
}
async function handleAuthResponse(res: Response): Promise<AuthPayload> {
if (!res.ok) {
let message = `Request failed (${res.status})`;
try {
const body = await res.json();
if (body?.message) message = body.message;
} catch {
// ignore
}
throw new Error(message);
}
return (await res.json()) as AuthPayload;
}
export async function apiRegister(input: RegisterInput): Promise<AuthPayload> {
const res = await fetch(`${API_BASE_URL}/api/auth/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(input),
});
return handleAuthResponse(res);
}
export async function apiLogin(input: LoginInput): Promise<AuthPayload> {
const res = await fetch(`${API_BASE_URL}/api/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(input),
});
return handleAuthResponse(res);
}
+87
View File
@@ -0,0 +1,87 @@
"use client";
import { useAuth } from "@/context/AuthContext";
import { useCallback, useMemo } from "react";
const DEFAULT_API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
export function useApi(baseUrl: string = DEFAULT_API_BASE) {
const { token } = useAuth();
const apiFetch = useCallback(
async <T = any>(path: string, options: RequestInit = {}): Promise<T> => {
// Allow either full URLs or just `/api/...`
const url = path.startsWith("http")
? path
: `${baseUrl.replace(/\/$/, "")}${path}`;
const headers: HeadersInit = {
"Content-Type": "application/json",
...(options.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
const res = await fetch(url, {
...options,
headers,
});
if (!res.ok) {
let bodyText = "";
try {
bodyText = await res.text();
} catch {
// ignore
}
throw new Error(
`API error ${res.status}: ${
bodyText || res.statusText || "Unknown error"
}`
);
}
// handle 204 / empty body safely
try {
return (await res.json()) as T;
} catch {
return {} as T;
}
},
[baseUrl, token] // 👈 only changes when baseUrl or token change
);
const get = useCallback(
<T = any>(path: string) => apiFetch<T>(path, { method: "GET" }),
[apiFetch]
);
const post = useCallback(
<T = any>(path: string, body?: unknown) =>
apiFetch<T>(path, {
method: "POST",
body: body !== undefined ? JSON.stringify(body) : undefined,
}),
[apiFetch]
);
const put = useCallback(
<T = any>(path: string, body?: unknown) =>
apiFetch<T>(path, {
method: "PUT",
body: body !== undefined ? JSON.stringify(body) : undefined,
}),
[apiFetch]
);
const del = useCallback(
<T = any>(path: string) => apiFetch<T>(path, { method: "DELETE" }),
[apiFetch]
);
// Optional: memoize the returned object so destructuring doesn't change refs
return useMemo(
() => ({ apiFetch, get, post, put, del }),
[apiFetch, get, post, put, del]
);
}