- Created new api/ directory with domain-specific API modules: - api/client.ts: Base API client with error handling - api/auth.ts: Authentication endpoints - api/exchange.ts: Exchange/price endpoints - api/trades.ts: User trade endpoints - api/profile.ts: Profile management endpoints - api/invites.ts: Invite endpoints - api/admin.ts: Admin endpoints - api/index.ts: Centralized exports - Migrated all API calls from ad-hoc api.get/post/put to typed domain APIs - Updated all imports across codebase - Fixed test mocks to use new API structure - Fixed type issues in validation utilities - Removed old api.ts file Benefits: - Type-safe endpoints (no more string typos) - Centralized API surface (easy to discover endpoints) - Better organization (domain-specific modules) - Uses generated OpenAPI types automatically
130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react";
|
|
|
|
import { authApi } from "./api";
|
|
import { components } from "./generated/api";
|
|
import { extractApiErrorMessage } from "./utils/error-handling";
|
|
|
|
// Permission type from generated OpenAPI schema
|
|
export type PermissionType = components["schemas"]["Permission"];
|
|
|
|
// Permission constants - derived from backend's Permission enum via OpenAPI.
|
|
// The type annotation ensures compile-time validation against the generated schema.
|
|
// Adding a new permission in the backend will cause a type error here until updated.
|
|
export const Permission: Record<string, PermissionType> = {
|
|
VIEW_AUDIT: "view_audit",
|
|
FETCH_PRICE: "fetch_price",
|
|
MANAGE_OWN_PROFILE: "manage_own_profile",
|
|
MANAGE_INVITES: "manage_invites",
|
|
VIEW_OWN_INVITES: "view_own_invites",
|
|
// Exchange permissions (regular users)
|
|
CREATE_EXCHANGE: "create_exchange",
|
|
VIEW_OWN_EXCHANGES: "view_own_exchanges",
|
|
CANCEL_OWN_EXCHANGE: "cancel_own_exchange",
|
|
// Availability/Exchange permissions (admin)
|
|
MANAGE_AVAILABILITY: "manage_availability",
|
|
VIEW_ALL_EXCHANGES: "view_all_exchanges",
|
|
CANCEL_ANY_EXCHANGE: "cancel_any_exchange",
|
|
COMPLETE_EXCHANGE: "complete_exchange",
|
|
} as const;
|
|
|
|
// Use generated type from OpenAPI schema
|
|
type User = components["schemas"]["UserResponse"];
|
|
|
|
interface AuthContextType {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (email: string, password: string, inviteIdentifier: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
hasPermission: (permission: PermissionType) => boolean;
|
|
hasRole: (role: string) => boolean;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
checkAuth();
|
|
}, []);
|
|
|
|
const checkAuth = async () => {
|
|
try {
|
|
const userData = await authApi.getMe();
|
|
setUser(userData);
|
|
} catch {
|
|
// Not authenticated
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const login = async (email: string, password: string) => {
|
|
try {
|
|
const userData = await authApi.login(email, password);
|
|
setUser(userData);
|
|
} catch (err) {
|
|
throw new Error(extractApiErrorMessage(err, "Login failed"));
|
|
}
|
|
};
|
|
|
|
const register = async (email: string, password: string, inviteIdentifier: string) => {
|
|
try {
|
|
const userData = await authApi.register(email, password, inviteIdentifier);
|
|
setUser(userData);
|
|
} catch (err) {
|
|
throw new Error(extractApiErrorMessage(err, "Registration failed"));
|
|
}
|
|
};
|
|
|
|
const logout = async () => {
|
|
try {
|
|
await authApi.logout();
|
|
} catch {
|
|
// Ignore errors on logout
|
|
}
|
|
setUser(null);
|
|
};
|
|
|
|
const hasPermission = useCallback(
|
|
(permission: PermissionType): boolean => {
|
|
return user?.permissions.includes(permission) ?? false;
|
|
},
|
|
[user]
|
|
);
|
|
|
|
const hasRole = useCallback(
|
|
(role: string): boolean => {
|
|
return user?.roles.includes(role) ?? false;
|
|
},
|
|
[user]
|
|
);
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
isLoading,
|
|
login,
|
|
register,
|
|
logout,
|
|
hasPermission,
|
|
hasRole,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within an AuthProvider");
|
|
}
|
|
return context;
|
|
}
|