243 lines
7.1 KiB
TypeScript
243 lines
7.1 KiB
TypeScript
import { authFetch, buildApiError, buildApiUrl } from './client';
|
||
|
||
const normalizeDigits = (value: string) =>
|
||
value
|
||
.replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)))
|
||
.replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)))
|
||
|
||
// --- Auth Endpoints ---
|
||
|
||
export const loginWithPassword = async (mobile: string, password: string) => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const response = await authFetch('/api/users/login/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ mobile: normalizedMobile, password })
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export interface SendOtpResponse {
|
||
detail: string
|
||
expires_in_seconds: number
|
||
expires_at?: string | null
|
||
}
|
||
|
||
export const sendOtp = async (mobile: string, mode: string): Promise<SendOtpResponse> => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const response = await authFetch('/api/users/otp/send/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ mobile: normalizedMobile, mode })
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const loginWithOtp = async (mobile: string, otp: string) => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const normalizedOtp = normalizeDigits(otp)
|
||
const response = await authFetch('/api/users/otp/login/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ mobile: normalizedMobile, code: normalizedOtp })
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const registerWithOtp = async (
|
||
mobile: string,
|
||
code: string,
|
||
password: string,
|
||
re_password: string,
|
||
first_name = "",
|
||
last_name = "",
|
||
) => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const normalizedCode = normalizeDigits(code)
|
||
const response = await authFetch("/api/users/register/", {
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
mobile: normalizedMobile,
|
||
code: normalizedCode,
|
||
password,
|
||
re_password,
|
||
first_name,
|
||
last_name,
|
||
}),
|
||
})
|
||
if (!response.ok) throw await buildApiError(response)
|
||
return response.json()
|
||
}
|
||
|
||
export const resetPasswordWithOtp = async (
|
||
mobile: string,
|
||
code: string,
|
||
password: string,
|
||
re_password: string,
|
||
) => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const normalizedCode = normalizeDigits(code)
|
||
const response = await authFetch("/api/users/password/reset/", {
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
mobile: normalizedMobile,
|
||
code: normalizedCode,
|
||
password,
|
||
re_password,
|
||
}),
|
||
})
|
||
if (!response.ok) throw await buildApiError(response)
|
||
return response.json()
|
||
}
|
||
|
||
export const changePassword = async (
|
||
old_password: string,
|
||
new_password: string,
|
||
re_password: string,
|
||
) => {
|
||
const response = await authFetch("/api/users/password/change/", {
|
||
method: "PATCH",
|
||
body: JSON.stringify({ old_password, new_password, re_password }),
|
||
})
|
||
if (!response.ok) throw await buildApiError(response)
|
||
return response.json()
|
||
}
|
||
|
||
export const startGoogleLogin = () => {
|
||
window.location.assign(buildApiUrl("/api/users/oauth/google/start/"));
|
||
};
|
||
|
||
export type GoogleOAuthFlowResponse =
|
||
| {
|
||
status: "authenticated";
|
||
access: string;
|
||
refresh: string;
|
||
}
|
||
| {
|
||
status: "collect_mobile";
|
||
email: string;
|
||
first_name: string;
|
||
last_name: string;
|
||
avatar_url: string;
|
||
resolution: "new_account" | "existing_email_claim";
|
||
mobile_hint?: string | null;
|
||
}
|
||
| {
|
||
status: "claim_required";
|
||
mobile: string;
|
||
detail?: string;
|
||
email: string;
|
||
resolution: "new_account" | "existing_email_claim" | "existing_mobile_claim";
|
||
mobile_hint?: string | null;
|
||
};
|
||
|
||
export const getGoogleOAuthFlow = async (flow: string): Promise<GoogleOAuthFlowResponse> => {
|
||
const response = await authFetch(`/api/users/oauth/google/flow/?flow=${encodeURIComponent(flow)}`, {
|
||
method: "GET",
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const completeGoogleOAuthSignup = async (
|
||
flow: string,
|
||
mobile: string,
|
||
): Promise<GoogleOAuthFlowResponse> => {
|
||
const normalizedMobile = normalizeDigits(mobile)
|
||
const response = await authFetch("/api/users/oauth/google/complete/", {
|
||
method: "POST",
|
||
body: JSON.stringify({ flow, mobile: normalizedMobile }),
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const sendGoogleOAuthClaimOtp = async (flow: string) => {
|
||
const response = await authFetch("/api/users/oauth/google/claim/send-otp/", {
|
||
method: "POST",
|
||
body: JSON.stringify({ flow }),
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const verifyGoogleOAuthClaim = async (
|
||
flow: string,
|
||
code: string,
|
||
): Promise<GoogleOAuthFlowResponse> => {
|
||
const normalizedCode = normalizeDigits(code)
|
||
const response = await authFetch("/api/users/oauth/google/claim/verify/", {
|
||
method: "POST",
|
||
body: JSON.stringify({ flow, code: normalizedCode }),
|
||
});
|
||
if (!response.ok) throw await buildApiError(response);
|
||
return response.json();
|
||
};
|
||
|
||
export const logoutUser = async (refreshToken: string) => {
|
||
const response = await authFetch('/api/users/logout/', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ refresh: refreshToken })
|
||
});
|
||
if (!response.ok) throw new Error("Logout failed");
|
||
return response.json();
|
||
};
|
||
|
||
// --- Profile Endpoints ---
|
||
|
||
export const getUserProfile = async () => {
|
||
const response = await authFetch('/api/users/me/', {
|
||
method: 'GET'
|
||
});
|
||
if (!response.ok) throw new Error('Failed to fetch profile');
|
||
return response.json();
|
||
};
|
||
|
||
export const updateUserProfile = async (data: Record<string, string>) => {
|
||
const response = await authFetch('/api/users/me/', {
|
||
method: 'PATCH',
|
||
body: JSON.stringify(data)
|
||
});
|
||
if (!response.ok) throw new Error('Failed to update profile');
|
||
return response.json();
|
||
};
|
||
|
||
export const updateProfilePicture = async (file: File) => {
|
||
const formData = new FormData();
|
||
formData.append('profile_picture', file);
|
||
|
||
const response = await authFetch('/api/users/profile/picture/', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
if (!response.ok) throw new Error('Failed to update profile picture');
|
||
return response.json();
|
||
};
|
||
|
||
export const removeProfilePicture = async () => {
|
||
const response = await authFetch(`/api/users/profile/picture/`, {
|
||
method: 'DELETE',
|
||
});
|
||
if (!response.ok) throw new Error('Failed to remove profile picture');
|
||
return response.json();
|
||
};
|
||
|
||
|
||
export interface SearchedUser {
|
||
id: number | string;
|
||
first_name: string;
|
||
last_name: string;
|
||
mobile: string;
|
||
profile_picture: string | null;
|
||
}
|
||
|
||
export const searchUserByExactMobile = async (mobile: string): Promise<SearchedUser | null> => {
|
||
try {
|
||
const response = await authFetch(`/api/users/search/?mobile=${encodeURIComponent(normalizeDigits(mobile))}`);
|
||
if (!response.ok) return null; // Returns null on 404 or other errors
|
||
return await response.json();
|
||
} catch (error) {
|
||
return null;
|
||
}
|
||
};
|