mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
256 lines
7.7 KiB
TypeScript
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;
|