Files
edr-platform/apps/edr-freight-web/portal/src/hooks/useAuth.ts

256 lines
7.7 KiB
TypeScript

import { api } from "@/services/api";
import type { ProfileTypeValue } from "@/services/companies.service";
import { companiesService } from "@/services/companies.service";
import type {
LoginPayload,
LoginResponse,
OtpResponse,
SignupPayload,
SignupResponse,
} from "@/types/auth";
import type { Result } from "@/utils/result";
import { extractApiError } from "@/utils/result";
import { useQuery, useQueryClient } from "@tanstack/react-query";
function setCookie(name: string, value: string, days: number) {
const expires = new Date();
expires.setDate(expires.getDate() + days);
document.cookie = `${name}=${value}; path=/; expires=${expires.toUTCString()}; SameSite=Lax`;
}
function getCookie(name: string): string | undefined {
return document.cookie
.split("; ")
.find((row) => row.startsWith(`${name}=`))
?.split("=")[1];
}
const useAuth = () => {
const queryClient = useQueryClient();
const hasToken = !!getCookie("auth-token");
const authQuery = useQuery(
api.auth.getMyInfo.queryOptions({
enabled: hasToken,
retry: false,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: 10 * 60 * 1000,
refetchOnWindowFocus: false,
}),
);
const companyQuery = useQuery(
api.companies.getInfo.queryOptions({
enabled: !!authQuery.data?.id,
retry: false,
staleTime: 10 * 60 * 1000,
refetchOnWindowFocus: false,
}),
);
const isPending = authQuery.isPending && hasToken;
const isAuthenticated = hasToken && !!authQuery.data && !authQuery.isError;
const login = async (
payload: LoginPayload,
): Promise<Result<LoginResponse>> => {
try {
const res = await api.auth.login.call(payload);
setCookie("auth-token", res.token, 7);
setCookie("refresh-token", res.refreshToken, 7);
await authQuery.refetch();
return { success: true, data: res };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const signup = async (
payload: SignupPayload,
): Promise<Result<SignupResponse>> => {
try {
const res = await api.auth.createUser.call(payload);
setCookie("auth-token", res.token, 7);
setCookie("refresh-token", res.refreshToken, 7);
await authQuery.refetch();
const otpCode = res.otp?.split(" ")?.[6] ?? "";
localStorage.setItem("otp", otpCode);
localStorage.setItem("otp-phone", payload.phoneNumber);
localStorage.setItem("otp-email", payload.email);
api.auth.sendOTP
.call({ phone: payload.phoneNumber, otp: otpCode })
.catch(() => { });
return { success: true, data: res };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const setPassword = async (data: {
newPassword: string;
confirmPassword: string;
}): Promise<Result<void>> => {
try {
const userId = authQuery.data?.id ?? "";
const email = localStorage.getItem("otp-email") ?? "";
const verificationCode = localStorage.getItem("otp") ?? "";
await api.auth.setPassword.call({
newPassword: data.newPassword,
confirmPassword: data.confirmPassword,
userId,
email,
verificationCode,
});
["userId", "otp", "otp-phone", "otp-email"].forEach((k) =>
localStorage.removeItem(k),
);
await queryClient.invalidateQueries({
queryKey: api.auth.getMyInfo.queryKey(),
});
return { success: true, data: undefined };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const verifyOTP = async (otp: string): Promise<Result<OtpResponse>> => {
try {
const phone = localStorage.getItem("otp-phone") ?? "";
const res = await api.auth.verifyOTP.call({ phone, otp });
return { success: true, data: res };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const sendOTP = async (otp: string): Promise<Result<OtpResponse>> => {
try {
const phone = localStorage.getItem("otp-phone") ?? "";
const res = await api.auth.sendOTP.call({ phone, otp });
return { success: true, data: res };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const generateVerificationCode = async (
type: string,
): Promise<Result<string>> => {
try {
const email = localStorage.getItem("otp-email") ?? "";
const phoneNumber = localStorage.getItem("otp-phone") ?? "";
const res = await api.auth.generateVerificationCode.call({
email,
phoneNumber,
type,
});
return { success: true, data: res };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
// Active-mode (importer/exporter) state, sourced from the persisted profile.
const companyInfo = isAuthenticated ? (companyQuery.data ?? null) : null;
const activeProfileType = companyInfo?.profile?.activeProfileType ?? null;
const activeCompanyProfileId =
companyInfo?.profile?.activeCompanyProfileId ?? null;
const companyType = companyInfo?.company?.type ?? null;
const companyStatus = companyInfo?.company?.status ?? null;
// A company can create bookings only once an admin has approved it (active).
const isCompanyApproved = companyStatus === "active";
const onboardingCompleted =
companyInfo?.profile?.onboardingCompleted ?? false;
const onboardingStep = companyInfo?.profile?.onboardingStep ?? null;
/** Refetch everything scoped to the active operational profile. */
const invalidateScopedData = async () => {
await Promise.all([
queryClient.invalidateQueries({
queryKey: api.companies.getInfo.queryKey(),
}),
queryClient.invalidateQueries({
queryKey: api.companies.getDashboard.queryKey(),
}),
queryClient.invalidateQueries({ queryKey: ["bookings"] }),
]);
};
const switchMode = async (
type: ProfileTypeValue,
): Promise<Result<void>> => {
try {
await api.companies.setActiveMode.call({ type });
await invalidateScopedData();
return { success: true, data: undefined };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const createProfileAndSwitch = async (
type: ProfileTypeValue,
licenseFiles: File[],
): Promise<Result<void>> => {
try {
const created = await api.companies.createCompanyProfile.call({ type });
if (licenseFiles.length > 0) {
await companiesService.uploadProfileLicense(created.id, licenseFiles);
}
await invalidateScopedData();
return { success: true, data: undefined };
} catch (err) {
return { success: false, error: extractApiError(err) };
}
};
const logout = async () => {
try {
await api.auth.logout.call();
} catch {
// proceed with client-side cleanup even if server call fails
}
[
"auth-token",
"refresh-token",
"auth-user",
"current-position-id",
"selected-position-id",
].forEach((name) => {
document.cookie = `${name}=; Max-Age=0; path=/`;
});
localStorage.clear();
queryClient.clear();
};
return {
isPending,
isAuthenticated,
user: isAuthenticated ? (authQuery.data ?? null) : null,
company: isAuthenticated ? (companyQuery.data ?? null) : null,
customer: isAuthenticated ? (companyQuery.data ?? null) : null,
activeProfileType,
activeCompanyProfileId,
companyType,
companyStatus,
isCompanyApproved,
onboardingCompleted,
onboardingStep,
switchMode,
createProfileAndSwitch,
login,
signup,
setPassword,
verifyOTP,
sendOTP,
generateVerificationCode,
logout,
authQuery,
companyQuery,
customerQuery: companyQuery,
};
};
export default useAuth;