arbret/frontend/app/auth-context.tsx

135 lines
3.7 KiB
TypeScript
Raw Normal View History

2025-12-18 22:08:31 +01:00
"use client";
2025-12-18 23:54:51 +01:00
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react";
2025-12-18 22:08:31 +01:00
import { api } from "./api";
2025-12-20 23:06:05 +01:00
import { components } from "./generated/api";
import { extractApiErrorMessage } from "./utils/error-handling";
2025-12-18 22:24:46 +01:00
// 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> = {
2025-12-18 23:33:32 +01:00
VIEW_AUDIT: "view_audit",
FETCH_PRICE: "fetch_price",
MANAGE_OWN_PROFILE: "manage_own_profile",
2025-12-20 11:12:11 +01:00
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",
2025-12-18 23:33:32 +01:00
} as const;
2025-12-20 23:06:05 +01:00
// Use generated type from OpenAPI schema
type User = components["schemas"]["UserResponse"];
2025-12-18 22:08:31 +01:00
interface AuthContextType {
user: User | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
2025-12-20 11:12:11 +01:00
register: (email: string, password: string, inviteIdentifier: string) => Promise<void>;
2025-12-18 22:24:46 +01:00
logout: () => Promise<void>;
2025-12-18 23:33:32 +01:00
hasPermission: (permission: PermissionType) => boolean;
hasRole: (role: string) => boolean;
2025-12-18 22:08:31 +01:00
}
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(() => {
2025-12-18 22:24:46 +01:00
checkAuth();
2025-12-18 22:08:31 +01:00
}, []);
2025-12-18 22:24:46 +01:00
const checkAuth = async () => {
2025-12-18 22:08:31 +01:00
try {
2025-12-19 11:08:19 +01:00
const userData = await api.get<User>("/api/auth/me");
setUser(userData);
2025-12-18 22:08:31 +01:00
} catch {
2025-12-18 22:24:46 +01:00
// Not authenticated
2025-12-18 22:08:31 +01:00
} finally {
setIsLoading(false);
}
};
const login = async (email: string, password: string) => {
2025-12-19 11:08:19 +01:00
try {
const userData = await api.post<User>("/api/auth/login", { email, password });
setUser(userData);
} catch (err) {
throw new Error(extractApiErrorMessage(err, "Login failed"));
2025-12-18 22:08:31 +01:00
}
};
2025-12-20 11:12:11 +01:00
const register = async (email: string, password: string, inviteIdentifier: string) => {
2025-12-19 11:08:19 +01:00
try {
2025-12-20 11:12:11 +01:00
const userData = await api.post<User>("/api/auth/register", {
email,
password,
invite_identifier: inviteIdentifier,
});
2025-12-19 11:08:19 +01:00
setUser(userData);
} catch (err) {
throw new Error(extractApiErrorMessage(err, "Registration failed"));
2025-12-18 22:08:31 +01:00
}
};
2025-12-18 22:24:46 +01:00
const logout = async () => {
2025-12-19 11:08:19 +01:00
try {
await api.post("/api/auth/logout");
} catch {
// Ignore errors on logout
}
2025-12-18 22:08:31 +01:00
setUser(null);
};
const hasPermission = useCallback(
(permission: PermissionType): boolean => {
return user?.permissions.includes(permission) ?? false;
},
[user]
);
2025-12-18 23:33:32 +01:00
const hasRole = useCallback(
(role: string): boolean => {
return user?.roles.includes(role) ?? false;
},
[user]
);
2025-12-18 23:33:32 +01:00
2025-12-18 22:08:31 +01:00
return (
2025-12-18 23:33:32 +01:00
<AuthContext.Provider
value={{
user,
isLoading,
login,
register,
logout,
hasPermission,
hasRole,
}}
>
2025-12-18 22:08:31 +01:00
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}