feat(frontend): rebuild auth around mobile-first flow
This commit is contained in:
133
src/lib/api.ts
133
src/lib/api.ts
@@ -179,6 +179,27 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async loginWithOtp(data: Types.UserOtpLoginSchema) {
|
||||
return this.request<Types.TokenSchema>('/api/auth/login/otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async sendOtp(data: Types.OtpSendSchema) {
|
||||
return this.request<Types.OtpSendResponseSchema>('/api/auth/otp/send', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async verifyRegisterOtp(data: Types.RegisterOtpVerifySchema) {
|
||||
return this.request<Types.MessageSchema>('/api/auth/otp/verify-register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async refreshToken(data: Types.TokenRefreshIn) {
|
||||
return this.request<Types.TokenSchema>('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
@@ -186,6 +207,60 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async resetPassword(data: Types.PasswordResetSchema) {
|
||||
return this.request<Types.MessageSchema>('/api/auth/reset-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async sendMobileVerificationOtp(data: Types.MobileOtpSendSchema) {
|
||||
return this.request<Types.OtpSendResponseSchema>('/api/auth/mobile/send-otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async verifyMobile(data: Types.MobileOtpVerifySchema) {
|
||||
return this.request<Types.UserProfileSchema>('/api/auth/mobile/verify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async startGoogleLogin() {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = `${this.baseUrl}/api/auth/oauth/google/start`;
|
||||
}
|
||||
}
|
||||
|
||||
async getGoogleFlow(flow: string) {
|
||||
return this.request<Types.GoogleFlowResponseSchema>(
|
||||
`/api/auth/oauth/google/flow?flow=${encodeURIComponent(flow)}`
|
||||
);
|
||||
}
|
||||
|
||||
async completeGoogleSignup(data: Types.GoogleCompleteSchema) {
|
||||
return this.request<Types.GoogleFlowResponseSchema>('/api/auth/oauth/google/complete', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async resendGoogleClaimOtp(flow: string) {
|
||||
return this.request<Types.MessageSchema>('/api/auth/oauth/google/claim/send-otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ flow }),
|
||||
});
|
||||
}
|
||||
|
||||
async verifyGoogleClaim(flow: string, code: string) {
|
||||
return this.request<Types.GoogleFlowResponseSchema>('/api/auth/oauth/google/claim/verify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ flow, code }),
|
||||
});
|
||||
}
|
||||
|
||||
async verifyEmail(token: string): Promise<Types.MessageSchema> {
|
||||
const url = `${this.baseUrl}/api/auth/verify-email/${encodeURIComponent(token)}`;
|
||||
const response = await fetch(url, { method: 'GET' });
|
||||
@@ -265,6 +340,17 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async getLegacyVerifyEmailMessage(token: string) {
|
||||
return this.request<Types.MessageSchema>(`/api/auth/verify-email/${encodeURIComponent(token)}`);
|
||||
}
|
||||
|
||||
async getLegacyResetTokenMessage(token: string) {
|
||||
return this.request<Types.MessageSchema>('/api/auth/reset-password-confirm', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ token }),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async checkUsername(username: string) {
|
||||
return this.request<Types.UsernameCheckSchema>(
|
||||
@@ -272,6 +358,12 @@ class ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
async checkMobile(mobile: string) {
|
||||
return this.request<Types.MobileLookupSchema>(
|
||||
`/api/auth/check-mobile?mobile=${encodeURIComponent(mobile)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Admin auth endpoints
|
||||
async listDeletedUsers() {
|
||||
return this.request<Types.UserProfileSchema[]>('/api/auth/users/deleted');
|
||||
@@ -681,6 +773,47 @@ class ApiClient {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getNotifications(params?: { limit?: number; offset?: number; type?: string }) {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.limit != null) query.set('limit', String(params.limit));
|
||||
if (params?.offset != null) query.set('offset', String(params.offset));
|
||||
if (params?.type) query.set('type', params.type);
|
||||
return this.request<Types.NotificationListSchema>(
|
||||
`/api/notifications/${query.toString() ? `?${query.toString()}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
async markNotificationSeen(id: string) {
|
||||
return this.request<Types.NotificationSeenResponseSchema>('/api/notifications/mark-seen', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteNotification(id: string) {
|
||||
return this.request<Types.NotificationDeleteResponseSchema>(`/api/notifications/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async markAllNotificationsRead(type?: string) {
|
||||
const query = type ? `?type=${encodeURIComponent(type)}` : '';
|
||||
return this.request<{ marked_read: number }>(`/api/notifications/mark-all-read${query}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
async issueNotificationStreamToken() {
|
||||
return this.request<Types.NotificationStreamTokenResponseSchema>('/api/notifications/stream-token', {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
buildNotificationStreamUrl(token: string) {
|
||||
const cleanBaseUrl = this.baseUrl.replace(/\/+$/, '');
|
||||
return `${cleanBaseUrl}/api/notifications/stream/?token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new ApiClient(API_BASE_URL);
|
||||
|
||||
129
src/lib/types.ts
129
src/lib/types.ts
@@ -12,6 +12,7 @@ export interface ErrorSchema {
|
||||
export interface TokenSchema {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type?: string;
|
||||
}
|
||||
|
||||
export interface MajorOption {
|
||||
@@ -21,7 +22,8 @@ export interface MajorOption {
|
||||
|
||||
export interface UserProfileSchema {
|
||||
id: number;
|
||||
email: string;
|
||||
email?: string | null;
|
||||
mobile?: string | null;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
@@ -36,6 +38,9 @@ export interface UserProfileSchema {
|
||||
date_joined: string;
|
||||
|
||||
is_email_verified?: boolean;
|
||||
is_mobile_verified?: boolean;
|
||||
requires_mobile_verification?: boolean;
|
||||
has_google_link?: boolean;
|
||||
is_active?: boolean;
|
||||
is_staff?: boolean;
|
||||
is_superuser?: boolean;
|
||||
@@ -47,7 +52,8 @@ export interface UserProfileSchema {
|
||||
export interface UserListSchema {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
email?: string | null;
|
||||
mobile?: string | null;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
full_name?: string | null;
|
||||
@@ -60,11 +66,13 @@ export interface UserListSchema {
|
||||
}
|
||||
|
||||
export interface UserRegistrationSchema {
|
||||
email: string;
|
||||
mobile: string;
|
||||
code: string;
|
||||
password: string;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email?: string | null;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
student_id?: string | null;
|
||||
year_of_study?: number | null;
|
||||
major?: string | null;
|
||||
@@ -72,6 +80,7 @@ export interface UserRegistrationSchema {
|
||||
}
|
||||
|
||||
export type UserUpdateSchema = {
|
||||
email?: string | null;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
bio?: string | null;
|
||||
@@ -83,25 +92,123 @@ export type UserUpdateSchema = {
|
||||
|
||||
|
||||
export interface UserLoginSchema {
|
||||
email: string;
|
||||
identifier: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UserOtpLoginSchema {
|
||||
mobile: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface RegisterOtpVerifySchema {
|
||||
mobile: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface OtpSendSchema {
|
||||
mobile: string;
|
||||
mode: "register" | "login" | "reset_password" | "verify_mobile" | "google_claim";
|
||||
}
|
||||
|
||||
export interface OtpSendResponseSchema {
|
||||
message: string;
|
||||
expires_in_seconds: number;
|
||||
expires_at: string;
|
||||
}
|
||||
|
||||
export interface TokenRefreshIn {
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface UsernameCheckSchema {
|
||||
available: boolean;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export interface PasswordResetRequestSchema {
|
||||
email: string;
|
||||
export interface MobileLookupSchema {
|
||||
exists: boolean;
|
||||
has_password: boolean;
|
||||
}
|
||||
|
||||
export interface PasswordResetConfirmSchema {
|
||||
export interface PasswordResetSchema {
|
||||
mobile: string;
|
||||
code: string;
|
||||
new_password: string;
|
||||
}
|
||||
|
||||
export interface MobileOtpSendSchema {
|
||||
mobile: string;
|
||||
}
|
||||
|
||||
export interface MobileOtpVerifySchema {
|
||||
mobile: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface GoogleFlowResponseSchema {
|
||||
status: "authenticated" | "collect_profile" | "claim_required" | "error";
|
||||
email?: string | null;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
avatar_url?: string | null;
|
||||
resolution?: "new_account" | "existing_email_claim" | "existing_mobile_claim" | null;
|
||||
mobile?: string | null;
|
||||
mobile_hint?: string | null;
|
||||
detail?: string | null;
|
||||
access_token?: string | null;
|
||||
refresh_token?: string | null;
|
||||
}
|
||||
|
||||
export interface GoogleCompleteSchema {
|
||||
flow: string;
|
||||
mobile: string;
|
||||
username?: string | null;
|
||||
student_id?: string | null;
|
||||
year_of_study?: number | null;
|
||||
major?: string | null;
|
||||
university?: string | null;
|
||||
first_name?: string | null;
|
||||
last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface NotificationSchema {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
message: string;
|
||||
level: "info" | "success" | "warning" | "error";
|
||||
created_at: string;
|
||||
is_seen: boolean;
|
||||
delete_on_seen: boolean;
|
||||
action_url?: string | null;
|
||||
entity_type?: string | null;
|
||||
entity_id?: string | number | null;
|
||||
meta?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface NotificationListSchema {
|
||||
count: number;
|
||||
unread_count: number;
|
||||
notifications: NotificationSchema[];
|
||||
}
|
||||
|
||||
export interface NotificationSeenResponseSchema {
|
||||
marked_read: boolean;
|
||||
notification_id?: string | null;
|
||||
deleted?: boolean;
|
||||
notification?: NotificationSchema | null;
|
||||
unread_count?: number | null;
|
||||
}
|
||||
|
||||
export interface NotificationDeleteResponseSchema {
|
||||
deleted: boolean;
|
||||
notification_id?: string | null;
|
||||
unread_count?: number | null;
|
||||
}
|
||||
|
||||
export interface NotificationStreamTokenResponseSchema {
|
||||
token: string;
|
||||
password: string;
|
||||
expires_in: number;
|
||||
}
|
||||
|
||||
// Blog Types
|
||||
|
||||
Reference in New Issue
Block a user