Add password auth, business browsing, dev proxy, and production env.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-23 13:14:52 +03:30
co-authored by Cursor
parent 3df62f9c6e
commit e07527c1db
23 changed files with 736 additions and 37 deletions
+25 -2
View File
@@ -1,3 +1,4 @@
import { normalizeIranMobile } from '@/utils/mobile';
import { api } from './client';
export type User = {
@@ -6,6 +7,7 @@ export type User = {
fullName: string | null;
role: string;
isActive: boolean;
hasPassword?: boolean;
};
export type RequestOtpResponse = {
@@ -21,17 +23,38 @@ export type VerifyOtpResponse = {
export function requestOtp(mobile: string) {
return api<RequestOtpResponse>('/auth/request-otp', {
method: 'POST',
body: JSON.stringify({ mobile }),
body: JSON.stringify({ mobile: normalizeIranMobile(mobile) }),
});
}
export function verifyOtp(mobile: string, code: string) {
return api<VerifyOtpResponse>('/auth/verify-otp', {
method: 'POST',
body: JSON.stringify({ mobile, code }),
body: JSON.stringify({ mobile: normalizeIranMobile(mobile), code }),
});
}
export function fetchMe(token: string) {
return api<User>('/auth/me', { token });
}
export function loginWithPassword(mobile: string, password: string) {
return api<VerifyOtpResponse>('/auth/login-password', {
method: 'POST',
body: JSON.stringify({
mobile: normalizeIranMobile(mobile),
password,
}),
});
}
export function setPassword(payload: {
password: string;
confirmPassword: string;
currentPassword?: string;
}) {
return api<User>('/auth/set-password', {
method: 'POST',
body: JSON.stringify(payload),
});
}
+6
View File
@@ -0,0 +1,6 @@
import { api } from './client';
import type { BusinessCategory } from './types';
export function fetchBusinessCategories() {
return api<BusinessCategory[]>('/business-categories');
}
+10
View File
@@ -0,0 +1,10 @@
import { api } from './client';
import type { BusinessService } from './types';
export function fetchBusinessServices(businessId: string) {
const search = new URLSearchParams({
businessId,
isActive: 'true',
});
return api<BusinessService[]>(`/business-services?${search}`);
}
+21
View File
@@ -0,0 +1,21 @@
import { api } from './client';
import type { Business } from './types';
export function fetchBusinesses(params?: {
categoryId?: string;
isActive?: boolean;
}) {
const search = new URLSearchParams();
if (params?.categoryId) {
search.set('categoryId', params.categoryId);
}
if (params?.isActive !== undefined) {
search.set('isActive', String(params.isActive));
}
const query = search.toString();
return api<Business[]>(`/businesses${query ? `?${query}` : ''}`);
}
export function fetchBusiness(id: string) {
return api<Business>(`/businesses/${id}`);
}
+46
View File
@@ -0,0 +1,46 @@
export type BusinessCategory = {
id: string;
slug: string;
title: string;
isActive: boolean;
};
export type BusinessImage = {
id: string;
url: string;
isCover: boolean;
sortOrder: number;
};
export type Business = {
id: string;
title: string;
slug: string;
description: string | null;
contactPhone: string | null;
address: string | null;
latitude: number | null;
longitude: number | null;
isActive: boolean;
categoryId: string;
category?: BusinessCategory;
images?: BusinessImage[];
};
export type ServiceTemplate = {
id: string;
title: string;
slug: string;
description: string | null;
defaultDurationMinutes: number;
};
export type BusinessService = {
id: string;
businessId: string;
serviceTemplateId: string;
price: string;
durationMinutes: number | null;
isActive: boolean;
serviceTemplate?: ServiceTemplate;
};