feat(frontend): rebuild auth around mobile-first flow
Some checks failed
Frontend CI/CD / build (push) Has been cancelled
Frontend CI/CD / deploy (push) Has been cancelled

This commit is contained in:
2026-05-21 10:28:03 +03:30
parent 66bb2fa107
commit f2b4cfce1a
17 changed files with 2703 additions and 752 deletions

View File

@@ -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);