fix test issues
Build & Deploy / Build & Push Docker Image (push) Successful in 3m1s
Test / Type Check (all packages) (push) Successful in 55s
Build & Deploy / Deploy to VPS (push) Successful in 5s
Test / API Unit Tests (push) Successful in 47s
Test / Homepage Unit Tests (push) Successful in 42s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 41s
Test / API Integration Tests (push) Successful in 1m0s
Build & Deploy / Build & Push Docker Image (push) Successful in 3m1s
Test / Type Check (all packages) (push) Successful in 55s
Build & Deploy / Deploy to VPS (push) Successful in 5s
Test / API Unit Tests (push) Successful in 47s
Test / Homepage Unit Tests (push) Successful in 42s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 41s
Test / API Integration Tests (push) Successful in 1m0s
This commit is contained in:
@@ -133,8 +133,8 @@ jobs:
|
|||||||
- run: npm run build --workspace @rentaldrivego/types
|
- run: npm run build --workspace @rentaldrivego/types
|
||||||
- run: npm run test:homepage
|
- run: npm run test:homepage
|
||||||
|
|
||||||
storefront-tests:
|
carplace-tests:
|
||||||
name: Storefront Unit Tests
|
name: Carplace Unit Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: type-check
|
needs: type-check
|
||||||
steps:
|
steps:
|
||||||
@@ -169,7 +169,7 @@ jobs:
|
|||||||
node -e "require('@rollup/rollup-linux-arm64-gnu')"
|
node -e "require('@rollup/rollup-linux-arm64-gnu')"
|
||||||
fi
|
fi
|
||||||
- run: npm run build --workspace @rentaldrivego/types
|
- run: npm run build --workspace @rentaldrivego/types
|
||||||
- run: npm run test:storefront
|
- run: npm run test:carplace
|
||||||
|
|
||||||
admin-tests:
|
admin-tests:
|
||||||
name: Admin Unit Tests
|
name: Admin Unit Tests
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ describe('carplace.repo public query and write boundaries', () => {
|
|||||||
where: {
|
where: {
|
||||||
id: 'vehicle_1',
|
id: 'vehicle_1',
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
company: { slug: 'atlas', status: { in: ['ACTIVE', 'TRIALING'] } },
|
company: {
|
||||||
|
OR: [{ slug: 'atlas' }, { brand: { subdomain: 'atlas' } }],
|
||||||
|
status: { in: ['ACTIVE', 'TRIALING'] },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
include: { company: { include: { brand: true } } },
|
include: { company: { include: { brand: true } } },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ vi.mock('../../modules/carplace/carplace.service', () => ({
|
|||||||
getCities: vi.fn(),
|
getCities: vi.fn(),
|
||||||
getListedCompanies: vi.fn(),
|
getListedCompanies: vi.fn(),
|
||||||
searchVehicles: vi.fn(),
|
searchVehicles: vi.fn(),
|
||||||
|
searchVehiclesPage: vi.fn(),
|
||||||
getVehicleDetail: vi.fn(),
|
getVehicleDetail: vi.fn(),
|
||||||
createCarplaceReservation: vi.fn(),
|
createCarplaceReservation: vi.fn(),
|
||||||
getCompanyPage: vi.fn(),
|
getCompanyPage: vi.fn(),
|
||||||
@@ -73,7 +74,11 @@ describe('employee, notification, and carplace API validation contracts', () =>
|
|||||||
vi.mocked(employeeService.updateLanguage).mockResolvedValue({ language: 'ar' } as never)
|
vi.mocked(employeeService.updateLanguage).mockResolvedValue({ language: 'ar' } as never)
|
||||||
vi.mocked(employeeService.resetPassword).mockResolvedValue({ message: 'Password updated successfully. You can now sign in.' } as never)
|
vi.mocked(employeeService.resetPassword).mockResolvedValue({ message: 'Password updated successfully. You can now sign in.' } as never)
|
||||||
vi.mocked(notificationService.setPreferences).mockResolvedValue({ success: true } as never)
|
vi.mocked(notificationService.setPreferences).mockResolvedValue({ success: true } as never)
|
||||||
vi.mocked(carplaceService.searchVehicles).mockResolvedValue({ companies: [], vehicles: [], pagination: { page: 1, pageSize: 20, total: 0 } } as never)
|
vi.mocked(carplaceService.searchVehiclesPage).mockResolvedValue({
|
||||||
|
items: [],
|
||||||
|
pagination: { page: 1, pageSize: 20, totalItems: 0, totalPages: 0 },
|
||||||
|
facets: { categories: [], transmissions: [], makes: [], price: { min: null, max: null } },
|
||||||
|
} as never)
|
||||||
vi.mocked(carplaceService.submitReview).mockResolvedValue({ id: 'review_1' } as never)
|
vi.mocked(carplaceService.submitReview).mockResolvedValue({ id: 'review_1' } as never)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -114,11 +119,12 @@ describe('employee, notification, and carplace API validation contracts', () =>
|
|||||||
const res = await request(app).get('/api/v1/carplace/search?city=Casablanca')
|
const res = await request(app).get('/api/v1/carplace/search?city=Casablanca')
|
||||||
|
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(carplaceService.searchVehicles).toHaveBeenCalledWith(expect.objectContaining({
|
expect(carplaceService.searchVehiclesPage).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
city: 'Casablanca',
|
city: 'Casablanca',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
}))
|
}))
|
||||||
|
expect(res.body.data.pagination).toEqual({ page: 1, pageSize: 20, totalItems: 0, totalPages: 0 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rejects out-of-range public review ratings before service execution', async () => {
|
it('rejects out-of-range public review ratings before service execution', async () => {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ vi.mock('../../modules/carplace/carplace.service', () => ({
|
|||||||
getCities: vi.fn(),
|
getCities: vi.fn(),
|
||||||
getListedCompanies: vi.fn(),
|
getListedCompanies: vi.fn(),
|
||||||
searchVehicles: vi.fn(),
|
searchVehicles: vi.fn(),
|
||||||
|
searchVehiclesPage: vi.fn(),
|
||||||
createCarplaceReservation: vi.fn(),
|
createCarplaceReservation: vi.fn(),
|
||||||
getReviewContext: vi.fn(),
|
getReviewContext: vi.fn(),
|
||||||
submitReview: vi.fn(),
|
submitReview: vi.fn(),
|
||||||
@@ -59,7 +60,11 @@ describe('public validation API contracts', () => {
|
|||||||
vi.mocked(siteService.checkAvailability).mockResolvedValue({ available: true, nextAvailableAt: null } as never)
|
vi.mocked(siteService.checkAvailability).mockResolvedValue({ available: true, nextAvailableAt: null } as never)
|
||||||
vi.mocked(siteService.initPayment).mockResolvedValue({ checkoutUrl: 'https://pay.example.test' } as never)
|
vi.mocked(siteService.initPayment).mockResolvedValue({ checkoutUrl: 'https://pay.example.test' } as never)
|
||||||
vi.mocked(siteService.handleContact).mockResolvedValue({ success: true } as never)
|
vi.mocked(siteService.handleContact).mockResolvedValue({ success: true } as never)
|
||||||
vi.mocked(carplaceService.searchVehicles).mockResolvedValue({ data: [], pagination: { page: 1, pageSize: 20 } } as never)
|
vi.mocked(carplaceService.searchVehiclesPage).mockResolvedValue({
|
||||||
|
items: [],
|
||||||
|
pagination: { page: 2, pageSize: 30, totalItems: 0, totalPages: 0 },
|
||||||
|
facets: { categories: [], transmissions: [], makes: [], price: { min: null, max: null } },
|
||||||
|
} as never)
|
||||||
vi.mocked(carplaceService.createCarplaceReservation).mockResolvedValue({ id: 'reservation_1' } as never)
|
vi.mocked(carplaceService.createCarplaceReservation).mockResolvedValue({ id: 'reservation_1' } as never)
|
||||||
vi.mocked(carplaceService.submitReview).mockResolvedValue({ id: 'review_1' } as never)
|
vi.mocked(carplaceService.submitReview).mockResolvedValue({ id: 'review_1' } as never)
|
||||||
})
|
})
|
||||||
@@ -124,19 +129,20 @@ describe('public validation API contracts', () => {
|
|||||||
const res = await request(app).get('/api/v1/carplace/search?city=Rabat&maxPrice=500&page=2&pageSize=30')
|
const res = await request(app).get('/api/v1/carplace/search?city=Rabat&maxPrice=500&page=2&pageSize=30')
|
||||||
|
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(carplaceService.searchVehicles).toHaveBeenCalledWith({
|
expect(carplaceService.searchVehiclesPage).toHaveBeenCalledWith({
|
||||||
city: 'Rabat',
|
city: 'Rabat',
|
||||||
maxPrice: 500,
|
maxPrice: 500,
|
||||||
page: 2,
|
page: 2,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
})
|
})
|
||||||
|
expect(res.body.data.pagination).toEqual({ page: 2, pageSize: 30, totalItems: 0, totalPages: 0 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rejects oversized carplace pagination before search execution', async () => {
|
it('rejects oversized carplace pagination before search execution', async () => {
|
||||||
const res = await request(app).get('/api/v1/carplace/search?pageSize=500')
|
const res = await request(app).get('/api/v1/carplace/search?pageSize=500')
|
||||||
|
|
||||||
expect(res.status).toBe(400)
|
expect(res.status).toBe(400)
|
||||||
expect(carplaceService.searchVehicles).not.toHaveBeenCalled()
|
expect(carplaceService.searchVehiclesPage).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('rejects carplace reservations with invalid public contact data before service execution', async () => {
|
it('rejects carplace reservations with invalid public contact data before service execution', async () => {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { CarplaceApiError, carplaceFetch, carplaceFetchOrDefault, carplacePost } from './api'
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
delete process.env.NEXT_PUBLIC_API_URL
|
delete process.env.NEXT_PUBLIC_API_URL
|
||||||
|
delete process.env.NEXT_PUBLIC_CARPLACE_DIRECT_API
|
||||||
delete process.env.API_INTERNAL_URL
|
delete process.env.API_INTERNAL_URL
|
||||||
vi.restoreAllMocks()
|
vi.restoreAllMocks()
|
||||||
|
Reflect.deleteProperty(globalThis, 'window')
|
||||||
Reflect.deleteProperty(globalThis, 'fetch')
|
Reflect.deleteProperty(globalThis, 'fetch')
|
||||||
|
vi.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('carplace API helpers', () => {
|
describe('carplace API helpers', () => {
|
||||||
@@ -13,6 +15,7 @@ describe('carplace API helpers', () => {
|
|||||||
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: [{ id: 'vehicle_1' }] }) }))
|
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: [{ id: 'vehicle_1' }] }) }))
|
||||||
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
||||||
|
|
||||||
|
const { carplaceFetch } = await import('./api')
|
||||||
await expect(carplaceFetch('/carplace/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }])
|
await expect(carplaceFetch('/carplace/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }])
|
||||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/carplace\/vehicles$/), {
|
expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/carplace\/vehicles$/), {
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
@@ -30,6 +33,7 @@ describe('carplace API helpers', () => {
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { carplaceFetch } = await import('./api')
|
||||||
await expect(carplaceFetch('/carplace/book')).rejects.toMatchObject({
|
await expect(carplaceFetch('/carplace/book')).rejects.toMatchObject({
|
||||||
name: 'CarplaceApiError',
|
name: 'CarplaceApiError',
|
||||||
message: 'Vehicle unavailable',
|
message: 'Vehicle unavailable',
|
||||||
@@ -45,14 +49,18 @@ describe('carplace API helpers', () => {
|
|||||||
value: vi.fn(async () => ({ ok: false, status: 500, json: async () => ({ message: 'Down' }) })),
|
value: vi.fn(async () => ({ ok: false, status: 500, json: async () => ({ message: 'Down' }) })),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { carplaceFetchOrDefault } = await import('./api')
|
||||||
await expect(carplaceFetchOrDefault('/carplace/homepage', { hero: null })).resolves.toEqual({ hero: null })
|
await expect(carplaceFetchOrDefault('/carplace/homepage', { hero: null })).resolves.toEqual({ hero: null })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('posts JSON payloads and unwraps successful responses', async () => {
|
it('posts JSON payloads and unwraps successful responses', async () => {
|
||||||
process.env.NEXT_PUBLIC_API_URL = 'https://api.example.com/api/v1'
|
process.env.NEXT_PUBLIC_API_URL = 'https://api.example.com/api/v1'
|
||||||
|
process.env.NEXT_PUBLIC_CARPLACE_DIRECT_API = 'true'
|
||||||
|
Object.defineProperty(globalThis, 'window', { configurable: true, value: {} })
|
||||||
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { id: 'reservation_1' } }) }))
|
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { id: 'reservation_1' } }) }))
|
||||||
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
||||||
|
|
||||||
|
const { carplacePost } = await import('./api')
|
||||||
await expect(carplacePost('/site/reservations', { vehicleId: 'vehicle_1' })).resolves.toEqual({ id: 'reservation_1' })
|
await expect(carplacePost('/site/reservations', { vehicleId: 'vehicle_1' })).resolves.toEqual({ id: 'reservation_1' })
|
||||||
expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/v1/site/reservations', expect.objectContaining({
|
expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/v1/site/reservations', expect.objectContaining({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -68,6 +76,7 @@ describe('carplace API helpers', () => {
|
|||||||
value: vi.fn(async () => ({ ok: false, status: 502, json: async () => { throw new Error('bad json') } })),
|
value: vi.fn(async () => ({ ok: false, status: 502, json: async () => { throw new Error('bad json') } })),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { carplaceFetch } = await import('./api')
|
||||||
await expect(carplaceFetch('/site/homepage')).rejects.toMatchObject({
|
await expect(carplaceFetch('/site/homepage')).rejects.toMatchObject({
|
||||||
name: 'CarplaceApiError',
|
name: 'CarplaceApiError',
|
||||||
message: 'Request failed',
|
message: 'Request failed',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ afterEach(() => {
|
|||||||
Reflect.deleteProperty(globalThis, 'window')
|
Reflect.deleteProperty(globalThis, 'window')
|
||||||
Reflect.deleteProperty(globalThis, 'fetch')
|
Reflect.deleteProperty(globalThis, 'fetch')
|
||||||
delete process.env.NEXT_PUBLIC_API_URL
|
delete process.env.NEXT_PUBLIC_API_URL
|
||||||
|
delete process.env.API_INTERNAL_URL
|
||||||
vi.resetModules()
|
vi.resetModules()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -54,6 +55,59 @@ describe('dashboard apiFetch', () => {
|
|||||||
expect(fetchMock).toHaveBeenCalledWith('https://api.rentaldrivego.ma/api/v1/auth/account/start', expect.any(Object))
|
expect(fetchMock).toHaveBeenCalledWith('https://api.rentaldrivego.ma/api/v1/auth/account/start', expect.any(Object))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['https://api.rentaldrivego.ma', 'https://api.rentaldrivego.ma/api/v1/auth/account/start'],
|
||||||
|
['https://api.rentaldrivego.ma/', 'https://api.rentaldrivego.ma/api/v1/auth/account/start'],
|
||||||
|
['https://api.rentaldrivego.ma/api', 'https://api.rentaldrivego.ma/api/v1/auth/account/start'],
|
||||||
|
['https://api.rentaldrivego.ma/api/v1', 'https://api.rentaldrivego.ma/api/v1/auth/account/start'],
|
||||||
|
['/dashboard/api/v1', '/dashboard/api/v1/auth/account/start'],
|
||||||
|
])('normalizes API base %s', async (configuredBase, expectedUrl) => {
|
||||||
|
installBrowser()
|
||||||
|
process.env.NEXT_PUBLIC_API_URL = configuredBase
|
||||||
|
const fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ data: { ok: true } }),
|
||||||
|
}))
|
||||||
|
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
||||||
|
|
||||||
|
const { apiFetch } = await import('./api')
|
||||||
|
await apiFetch('/auth/account/start', { method: 'POST', body: JSON.stringify({}) })
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(expectedUrl, expect.any(Object))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to the dashboard proxy when no public API URL is configured', async () => {
|
||||||
|
installBrowser()
|
||||||
|
const fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ data: { ok: true } }),
|
||||||
|
}))
|
||||||
|
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
||||||
|
|
||||||
|
const { apiFetch } = await import('./api')
|
||||||
|
await apiFetch('/auth/account/start')
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith('/dashboard/api/v1/auth/account/start', expect.any(Object))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not add a dashboard prefix when an absolute API origin is configured', async () => {
|
||||||
|
installBrowser()
|
||||||
|
process.env.NEXT_PUBLIC_API_URL = 'https://api.rentaldrivego.ma/api/v1'
|
||||||
|
const fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ data: { ok: true } }),
|
||||||
|
}))
|
||||||
|
Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock })
|
||||||
|
|
||||||
|
const { apiFetch } = await import('./api')
|
||||||
|
await apiFetch('/auth/account/start')
|
||||||
|
|
||||||
|
const calledUrl = (fetchMock as any).mock.calls[0]?.[0]
|
||||||
|
expect(calledUrl).toBe('https://api.rentaldrivego.ma/api/v1/auth/account/start')
|
||||||
|
expect(calledUrl).not.toContain('/dashboard')
|
||||||
|
expect(calledUrl).not.toContain('/api/v1/api/v1')
|
||||||
|
})
|
||||||
|
|
||||||
it('does not force JSON content type for FormData payloads', async () => {
|
it('does not force JSON content type for FormData payloads', async () => {
|
||||||
installBrowser()
|
installBrowser()
|
||||||
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { uploaded: true } }) }))
|
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { uploaded: true } }) }))
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
function normalizeApiBase(value: string): string {
|
export function normalizeApiBase(value: string): string {
|
||||||
const base = value.replace(/\/+$/, '')
|
const base = value.replace(/\/+$/, '')
|
||||||
|
if (/\/api$/.test(base)) return `${base}/v1`
|
||||||
return /\/api\/v1$/.test(base) ? base : `${base}/api/v1`
|
return /\/api\/v1$/.test(base) ? base : `${base}/api/v1`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const API_BASE = normalizeApiBase(
|
export function resolveApiBase(): string {
|
||||||
typeof window === 'undefined'
|
const value = typeof window === 'undefined'
|
||||||
? (process.env.API_INTERNAL_URL || 'http://localhost:4000/api/v1')
|
? (process.env.API_INTERNAL_URL || 'http://localhost:4000/api/v1')
|
||||||
: (process.env.NEXT_PUBLIC_DASHBOARD_DIRECT_API === 'true' && process.env.NEXT_PUBLIC_API_URL
|
: (process.env.NEXT_PUBLIC_API_URL || '/dashboard/api/v1')
|
||||||
? process.env.NEXT_PUBLIC_API_URL
|
|
||||||
: '/dashboard/api/v1'),
|
return normalizeApiBase(value)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
export const API_BASE = resolveApiBase()
|
||||||
|
|
||||||
export const EMPLOYEE_PROFILE_KEY = 'employee_profile'
|
export const EMPLOYEE_PROFILE_KEY = 'employee_profile'
|
||||||
|
|
||||||
@@ -24,7 +27,7 @@ export async function apiFetch<T>(path: string, options?: RequestInit): Promise<
|
|||||||
headers['Content-Type'] = 'application/json'
|
headers['Content-Type'] = 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_BASE}${path}`, {
|
const res = await fetch(`${resolveApiBase()}${path}`, {
|
||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
@@ -49,7 +52,7 @@ export async function apiFetch<T>(path: string, options?: RequestInit): Promise<
|
|||||||
|
|
||||||
export async function apiFetchServer<T>(path: string, token: string, options?: RequestInit): Promise<T> {
|
export async function apiFetchServer<T>(path: string, token: string, options?: RequestInit): Promise<T> {
|
||||||
const isFormData = typeof FormData !== 'undefined' && options?.body instanceof FormData
|
const isFormData = typeof FormData !== 'undefined' && options?.body instanceof FormData
|
||||||
const res = await fetch(`${API_BASE}${path}`, {
|
const res = await fetch(`${resolveApiBase()}${path}`, {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ export const demoLeadSchema = z
|
|||||||
.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'invalid_email'),
|
.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'invalid_email'),
|
||||||
),
|
),
|
||||||
company: compactText(160),
|
company: compactText(160),
|
||||||
fleetSize: z.enum(fleetSizeValues, { error: 'invalid_fleet_size' }),
|
fleetSize: z.enum(fleetSizeValues, { message: 'invalid_fleet_size' }),
|
||||||
market: optionalCompactText(100),
|
market: optionalCompactText(100),
|
||||||
preferredLanguage: z.enum(['en', 'fr', 'ar']).optional(),
|
preferredLanguage: z.enum(['en', 'fr', 'ar']).optional(),
|
||||||
message: optionalCompactText(1_000),
|
message: optionalCompactText(1_000),
|
||||||
idempotencyKey: z.string().uuid('invalid_idempotency_key'),
|
idempotencyKey: z.string().uuid('invalid_idempotency_key'),
|
||||||
source: z.enum(demoSourceValues, { error: 'invalid_source' }),
|
source: z.enum(demoSourceValues, { message: 'invalid_source' }),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user