Initial commit for new panel
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
requestErrorHandler,
|
||||
requestInterceptor,
|
||||
} from "@/middleware/request-interceptor";
|
||||
import {
|
||||
responseErrorHandler,
|
||||
responseInterceptor,
|
||||
} from "@/middleware/response-interceptor";
|
||||
import axios from "axios";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
api.interceptors.request.use(requestInterceptor, requestErrorHandler);
|
||||
|
||||
api.interceptors.response.use(responseInterceptor, responseErrorHandler);
|
||||
|
||||
export default api;
|
||||
@@ -0,0 +1,38 @@
|
||||
export const API_ENDPOINTS = {
|
||||
POSTS: {
|
||||
READ_ALL: "posts ",
|
||||
},
|
||||
USER: {
|
||||
LOGIN: "profile/login",
|
||||
LOGOUT: "profile/logout",
|
||||
REFRESH_TOKEN: "profile/refresh-token",
|
||||
ADMINS: "users",
|
||||
ADMIN_DETAILS: (id: string) => `users/${id}`,
|
||||
},
|
||||
PROFILE: {
|
||||
READ: "profile",
|
||||
},
|
||||
COMPANIES: {
|
||||
READ_ALL: "companies",
|
||||
CREATE: "companies",
|
||||
UPDATE: (id: string) => `companies/${id}`,
|
||||
DELETE: (id: string) => `companies/${id}`,
|
||||
DETAILS: (id: string) => `companies/${id}`,
|
||||
},
|
||||
TENDERS: {
|
||||
READ_ALL: "tenders",
|
||||
CREATE: "tenders",
|
||||
UPDATE: (id: string) => `tenders/${id}`,
|
||||
DELETE: (id: string) => `tenders/${id}`,
|
||||
DETAILS: (id: string) => `tenders/${id}`,
|
||||
},
|
||||
CUSTOMERS: {
|
||||
READ_ALL: "customers",
|
||||
CREATE: "customers",
|
||||
UPDATE: (id: string) => `customers/${id}`,
|
||||
DELETE: (id: string) => `customers/${id}`,
|
||||
DETAILS: (id: string) => `customers/${id}`,
|
||||
ASSIGN_COMPANY_TO_CUSTOMER: (id: string) =>
|
||||
`customers/${id}/companies/assign`,
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as api } from "./axios";
|
||||
export * from "./endpoints";
|
||||
export * from "./types";
|
||||
export * from "./services";
|
||||
|
||||
export { default } from "./axios";
|
||||
@@ -0,0 +1,60 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import {
|
||||
ApiResponse,
|
||||
CompaniesListResponseSchema,
|
||||
TCompaniesResponse,
|
||||
} from "../types";
|
||||
|
||||
import { ICreateCompanyCredentials } from "../types";
|
||||
|
||||
export const companiesService = {
|
||||
getCompanies: async (
|
||||
params?: Record<string, any>,
|
||||
): Promise<ApiResponse<TCompaniesResponse>> => {
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.COMPANIES.READ_ALL, {
|
||||
params,
|
||||
});
|
||||
|
||||
return CompaniesListResponseSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Companies Services Read all:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
createCompany: async (credentials: ICreateCompanyCredentials) => {
|
||||
const response = await api.post(
|
||||
API_ENDPOINTS.COMPANIES.CREATE,
|
||||
credentials,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
updateCompany: async ({
|
||||
id,
|
||||
credentials,
|
||||
}: {
|
||||
id: string;
|
||||
credentials: ICreateCompanyCredentials;
|
||||
}) => {
|
||||
const response = await api.put(
|
||||
API_ENDPOINTS.COMPANIES.UPDATE(id),
|
||||
credentials,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
companyDetails: async (id: string) => {
|
||||
const response = await api.get(API_ENDPOINTS.COMPANIES.DETAILS(id));
|
||||
return response.data;
|
||||
},
|
||||
deleteCompany: async (id: string) => {
|
||||
try {
|
||||
return (await api.delete(API_ENDPOINTS.COMPANIES.DELETE(id))).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Companies Services Delete:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import {
|
||||
CreateCustomerCredentials,
|
||||
CustomerListResponseSchema,
|
||||
TAssignCustomerToCompanyCredentials,
|
||||
} from "../types/Customers";
|
||||
|
||||
export const customersService = {
|
||||
getCustomers: async (params?: Record<string, any>) => {
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.CUSTOMERS.READ_ALL, {
|
||||
params,
|
||||
});
|
||||
return CustomerListResponseSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Customers Services Read all:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
deleteCustomer: async (id: string) => {
|
||||
try {
|
||||
return (await api.delete(API_ENDPOINTS.CUSTOMERS.DELETE(id))).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Customers Services Delete:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
createCustomer: async (credentials: CreateCustomerCredentials) => {
|
||||
try {
|
||||
return (await api.post(API_ENDPOINTS.CUSTOMERS.CREATE, credentials)).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Customers Services Create:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
updateCustomer: async ({
|
||||
id,
|
||||
credentials,
|
||||
}: {
|
||||
id: string;
|
||||
credentials: CreateCustomerCredentials;
|
||||
}) => {
|
||||
try {
|
||||
return (await api.put(API_ENDPOINTS.CUSTOMERS.UPDATE(id), credentials))
|
||||
.data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Customers Services Update:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
assignCompanyToCustomer: async ({
|
||||
credentials,
|
||||
id,
|
||||
}: {
|
||||
id: string;
|
||||
credentials: TAssignCustomerToCompanyCredentials;
|
||||
}): Promise<any> => {
|
||||
try {
|
||||
return (
|
||||
await api.post(
|
||||
API_ENDPOINTS.CUSTOMERS.ASSIGN_COMPANY_TO_CUSTOMER(id),
|
||||
credentials,
|
||||
)
|
||||
).data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"ERROR caught in Customers Service => Assign company to customer",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./user-services";
|
||||
export * from "./companies-service";
|
||||
export * from "./tenders-service";
|
||||
export * from "./profile-service";
|
||||
@@ -0,0 +1,15 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import { CurrentUserProfileSchema } from "../types";
|
||||
export const profileService = {
|
||||
getProfile: async () => {
|
||||
try {
|
||||
const response = (await api.get(API_ENDPOINTS.PROFILE.READ)).data;
|
||||
|
||||
return CurrentUserProfileSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Profile Service: Get Profile", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import { ApiResponse } from "../types";
|
||||
import { TTenderResponse } from "../types/Tenders";
|
||||
|
||||
export const tendersService = {
|
||||
tendersList: async (
|
||||
params?: Record<string, any>,
|
||||
): Promise<ApiResponse<TTenderResponse>> => {
|
||||
try {
|
||||
return (await api.get(API_ENDPOINTS.TENDERS.READ_ALL, { params })).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in Tenders Services Read all:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import api from "../axios";
|
||||
import { API_ENDPOINTS } from "../endpoints";
|
||||
import {
|
||||
AdminListResponseSchema,
|
||||
ApiResponse,
|
||||
IAdminListResponse,
|
||||
ICreateAdminCredentials,
|
||||
ILoginCredentials,
|
||||
ILoginResponse,
|
||||
ILogoutResponse,
|
||||
LoginResponseSchema,
|
||||
LogoutResponseSchema,
|
||||
} from "../types";
|
||||
|
||||
export const userService = {
|
||||
login: async (
|
||||
credentials: ILoginCredentials
|
||||
): Promise<ApiResponse<ILoginResponse>> => {
|
||||
try {
|
||||
const response = await api.post(API_ENDPOINTS.USER.LOGIN, credentials);
|
||||
|
||||
return LoginResponseSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Login:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
logout: async (): Promise<ApiResponse<ILogoutResponse>> => {
|
||||
try {
|
||||
const response = await api.delete(API_ENDPOINTS.USER.LOGOUT);
|
||||
return LogoutResponseSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Logout", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
adminsList: async (
|
||||
params?: Record<string, any>
|
||||
): Promise<ApiResponse<IAdminListResponse>> => {
|
||||
try {
|
||||
const response = await api.get(API_ENDPOINTS.USER.ADMINS, { params });
|
||||
return AdminListResponseSchema.parse(response.data);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service User List", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
createAdmin: async (credentials: ICreateAdminCredentials) => {
|
||||
try {
|
||||
return (await api.post(API_ENDPOINTS.USER.ADMINS, credentials)).data
|
||||
.message;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Create Admin", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
updateAdmin: async ({
|
||||
id,
|
||||
credentials,
|
||||
}: {
|
||||
id: string;
|
||||
credentials: ICreateAdminCredentials;
|
||||
}) => {
|
||||
try {
|
||||
return (await api.put(API_ENDPOINTS.USER.ADMIN_DETAILS(id), credentials))
|
||||
.data.message;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Update Admin", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
adminDetails: async (id: string) => {
|
||||
try {
|
||||
return (await api.get(API_ENDPOINTS.USER.ADMIN_DETAILS(id))).data;
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Admin Details", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
deleteAdmin: async (
|
||||
id: string
|
||||
): Promise<ApiResponse<{ message: string }>> => {
|
||||
try {
|
||||
return await api.delete(`${API_ENDPOINTS.USER.ADMINS}/${id}`);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught in User Service Delete Admin", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createApiResponseSchema } from "./Factory";
|
||||
export interface IAddress {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface IContactPerson {
|
||||
full_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
mobile: string;
|
||||
position: string;
|
||||
is_primary: boolean;
|
||||
}
|
||||
|
||||
export interface ITags {
|
||||
keywords: string[];
|
||||
categories: string[];
|
||||
specializations: string[];
|
||||
certifications: string[];
|
||||
cpv_codes: string[];
|
||||
}
|
||||
|
||||
export interface ICreateCompanyCredentials {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
website: string;
|
||||
type: string;
|
||||
description: string;
|
||||
founded_year: number;
|
||||
employee_count: number;
|
||||
annual_revenue: number;
|
||||
currency: string;
|
||||
industry: string;
|
||||
language: string;
|
||||
tax_id: string;
|
||||
registration_number: string;
|
||||
timezone: string;
|
||||
address: IAddress;
|
||||
contact_person: IContactPerson;
|
||||
tags: ITags;
|
||||
}
|
||||
|
||||
export const companySchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email().optional(),
|
||||
phone: z.string().optional(),
|
||||
website: z.string(),
|
||||
type: z.string(),
|
||||
description: z.string(),
|
||||
founded_year: z.number(),
|
||||
employee_count: z.number(),
|
||||
annual_revenue: z.number(),
|
||||
currency: z.string(),
|
||||
industry: z.string(),
|
||||
language: z.string(),
|
||||
tax_id: z.string(),
|
||||
registration_number: z.string(),
|
||||
timezone: z.string(),
|
||||
address: z.object({
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
postal_code: z.string(),
|
||||
country: z.string(),
|
||||
}),
|
||||
contact_person: z.object({
|
||||
full_name: z.string(),
|
||||
first_name: z.string(),
|
||||
last_name: z.string(),
|
||||
email: z.string().email(),
|
||||
phone: z.string(),
|
||||
mobile: z.string(),
|
||||
position: z.string(),
|
||||
is_primary: z.boolean(),
|
||||
}),
|
||||
tags: z.object({
|
||||
keywords: z.array(z.string()).optional(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
specializations: z.array(z.string()).optional(),
|
||||
certifications: z.array(z.string()).optional(),
|
||||
cpv_codes: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const companiesResponseSchema = z.object({
|
||||
companies: z.array(companySchema),
|
||||
offset: z.number(),
|
||||
limit: z.number(),
|
||||
total: z.number(),
|
||||
});
|
||||
|
||||
export const companyDetailsApiResponseSchema =
|
||||
createApiResponseSchema(companySchema);
|
||||
export const CompaniesListResponseSchema = createApiResponseSchema(
|
||||
companiesResponseSchema
|
||||
);
|
||||
export type TCompany = z.infer<typeof companySchema>;
|
||||
export type TCompaniesResponse = z.infer<typeof companiesResponseSchema>;
|
||||
@@ -0,0 +1,114 @@
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "./Factory";
|
||||
|
||||
const customersSchema = z.object({
|
||||
companies: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
.or(z.null())
|
||||
.optional(),
|
||||
compliance_notes: z.any(),
|
||||
|
||||
created_at: z.number(),
|
||||
|
||||
email: z.string().email(),
|
||||
|
||||
full_name: z.string(),
|
||||
id: z.string(),
|
||||
|
||||
phone: z.string(),
|
||||
|
||||
status: z.string(),
|
||||
|
||||
type: z.string(),
|
||||
updated_at: z.number(),
|
||||
updated_by: z.any(),
|
||||
username: z.string(),
|
||||
});
|
||||
|
||||
export const customersListResponse = z.object({
|
||||
customers: z.array(customersSchema),
|
||||
});
|
||||
const createCustomerCredentials = z.object({
|
||||
address: z
|
||||
.object({
|
||||
address_type: z.string(),
|
||||
city: z.string(),
|
||||
country: z.string(),
|
||||
is_default: z.boolean(),
|
||||
postal_code: z.string(),
|
||||
state: z.string(),
|
||||
street: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
annual_revenue: z.number().min(0).max(999999999999).optional(),
|
||||
business_type: z.string().min(2).max(100).optional(),
|
||||
company_ids: z.array(z.string()).optional(),
|
||||
contact_person: z
|
||||
.object({
|
||||
email: z.string(),
|
||||
first_name: z.string(),
|
||||
full_name: z.string(),
|
||||
is_primary: z.boolean(),
|
||||
last_name: z.string(),
|
||||
mobile: z.string(),
|
||||
phone: z.string(),
|
||||
position: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
currency: z
|
||||
.enum([
|
||||
"USD",
|
||||
"EUR",
|
||||
"GBP",
|
||||
"JPY",
|
||||
"CAD",
|
||||
"AUD",
|
||||
"CHF",
|
||||
"CNY",
|
||||
"INR",
|
||||
"BRL",
|
||||
])
|
||||
.optional(),
|
||||
email: z.string().email(),
|
||||
employee_count: z.number().int().min(1).max(100000).optional(),
|
||||
first_name: z.string().min(2).max(50).optional(),
|
||||
founded_year: z.number().int().min(1800).max(2100).optional(),
|
||||
full_name: z.string().min(2).max(100).optional(),
|
||||
industry: z.string().min(2).max(100).optional(),
|
||||
language: z.enum(["en", "ar", "fr", "es", "de", "zh", "ja", "ko"]).optional(),
|
||||
last_name: z.string().min(2).max(50).optional(),
|
||||
mobile: z.string().min(10).max(20).optional(),
|
||||
password: z.string().min(8).max(128),
|
||||
phone: z.string().min(10).max(20).optional(),
|
||||
registration_number: z.string().min(5).max(50).optional(),
|
||||
tax_id: z.string().min(5).max(50).optional(),
|
||||
timezone: z.string().min(3).max(50).optional(),
|
||||
type: z.string(),
|
||||
username: z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(30)
|
||||
.regex(
|
||||
/^[a-zA-Z0-9]+$/,
|
||||
"Username must contain only alphanumeric characters",
|
||||
),
|
||||
});
|
||||
const assignCustomerToCompanySchema = z.object({
|
||||
company_ids: z.array(z.string()),
|
||||
});
|
||||
export const CustomerListResponseSchema = createApiResponseSchema(
|
||||
customersListResponse,
|
||||
);
|
||||
export type TAssignCustomerToCompanyCredentials = z.infer<
|
||||
typeof assignCustomerToCompanySchema
|
||||
>;
|
||||
export type TCustomer = z.infer<typeof customersSchema>;
|
||||
export type CreateCustomerCredentials = z.infer<
|
||||
typeof createCustomerCredentials
|
||||
>;
|
||||
export type TCustomersListResponse = z.infer<typeof customersListResponse>;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
const MetaDataSchema = z.object({
|
||||
limit: z.number(),
|
||||
offset: z.number(),
|
||||
page: z.number(),
|
||||
pages: z.number(),
|
||||
total: z.number(),
|
||||
});
|
||||
export function createApiResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
|
||||
return z.object({
|
||||
data: dataSchema,
|
||||
meta: z
|
||||
.object({
|
||||
limit: z.number(),
|
||||
offset: z.number(),
|
||||
page: z.number(),
|
||||
pages: z.number(),
|
||||
total: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
message: z.string(),
|
||||
code: z.number().optional(),
|
||||
success: z.boolean(),
|
||||
});
|
||||
}
|
||||
export type IMetaData = z.infer<typeof MetaDataSchema>;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const IPostSchema = z.object({
|
||||
userId: z.number(),
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
body: z.string(),
|
||||
});
|
||||
|
||||
export type IPost = z.infer<typeof IPostSchema>;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod";
|
||||
export const CurrentUserProfileSchema = z.object({
|
||||
company_id: z.string().optional(),
|
||||
created_at: z.number().optional(),
|
||||
created_by: z.string().optional(),
|
||||
department: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
full_name: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
is_verified: z.boolean().optional(),
|
||||
last_login_at: z.number().optional(),
|
||||
phone: z.string().optional(),
|
||||
position: z.string().optional(),
|
||||
profile_image: z.string().optional(),
|
||||
role: z.string().optional(),
|
||||
status: z.string().or(z.number()).optional(),
|
||||
updated_at: z.string().or(z.number()).optional(),
|
||||
updated_by: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
});
|
||||
export type TCurrentUser = z.infer<typeof CurrentUserProfileSchema>;
|
||||
@@ -0,0 +1,107 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { createApiResponseSchema } from "./Factory";
|
||||
export interface IAddress {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface IContactPerson {
|
||||
full_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
mobile: string;
|
||||
position: string;
|
||||
is_primary: boolean;
|
||||
}
|
||||
|
||||
export interface ITags {
|
||||
keywords: string[];
|
||||
categories: string[];
|
||||
specializations: string[];
|
||||
certifications: string[];
|
||||
cpv_codes: string[];
|
||||
}
|
||||
|
||||
export interface ICreateCompanyCredentials {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
website: string;
|
||||
type: string;
|
||||
description: string;
|
||||
founded_year: number;
|
||||
employee_count: number;
|
||||
annual_revenue: number;
|
||||
currency: string;
|
||||
industry: string;
|
||||
language: string;
|
||||
tax_id: string;
|
||||
registration_number: string;
|
||||
timezone: string;
|
||||
address: IAddress;
|
||||
contact_person?: IContactPerson;
|
||||
tags: ITags;
|
||||
}
|
||||
|
||||
export const companySchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email().optional(),
|
||||
phone: z.string().optional(),
|
||||
website: z.string(),
|
||||
type: z.string(),
|
||||
description: z.string(),
|
||||
founded_year: z.number(),
|
||||
employee_count: z.number(),
|
||||
annual_revenue: z.number(),
|
||||
currency: z.string(),
|
||||
industry: z.string(),
|
||||
language: z.string(),
|
||||
tax_id: z.string(),
|
||||
registration_number: z.string(),
|
||||
timezone: z.string(),
|
||||
address: z.object({
|
||||
street: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
postal_code: z.string(),
|
||||
country: z.string(),
|
||||
}),
|
||||
contact_person: z
|
||||
.object({
|
||||
full_name: z.string(),
|
||||
first_name: z.string(),
|
||||
last_name: z.string(),
|
||||
email: z.string().email(),
|
||||
phone: z.string(),
|
||||
mobile: z.string(),
|
||||
position: z.string(),
|
||||
is_primary: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
tags: z.object({
|
||||
keywords: z.array(z.string()).or(z.null()).optional(),
|
||||
categories: z.array(z.string()).or(z.null()).optional(),
|
||||
specializations: z.array(z.string()).or(z.null()).optional(),
|
||||
certifications: z.array(z.string()).or(z.null()).optional(),
|
||||
cpv_codes: z.array(z.string()).or(z.null()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const companiesResponseSchema = z.object({
|
||||
companies: z.array(companySchema).or(z.null()),
|
||||
});
|
||||
|
||||
export const companyDetailsApiResponseSchema =
|
||||
createApiResponseSchema(companySchema);
|
||||
export const CompaniesListResponseSchema = createApiResponseSchema(
|
||||
companiesResponseSchema,
|
||||
);
|
||||
export type TCompany = z.infer<typeof companySchema>;
|
||||
export type TCompaniesResponse = z.infer<typeof companiesResponseSchema>;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const TenderSchema = z.object({
|
||||
id: z.string(),
|
||||
buyer_organization: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
application_deadline: z.number(),
|
||||
country_code: z.string(),
|
||||
currency: z.string(),
|
||||
description: z.string(),
|
||||
duration: z.string(),
|
||||
duration_unit: z.string(),
|
||||
estimated_value: z.number(),
|
||||
notice_publication_id: z.string(),
|
||||
procedure_code: z.string(),
|
||||
procurement_type_code: z.string(),
|
||||
publication_date: z.number(),
|
||||
status: z.string(),
|
||||
submission_deadline: z.number(),
|
||||
submission_url: z.string(),
|
||||
tender_deadline: z.number(),
|
||||
tender_id: z.string(),
|
||||
title: z.string(),
|
||||
});
|
||||
export const TendersResponse = z.object({
|
||||
companies: z.array(TenderSchema).or(z.null()),
|
||||
});
|
||||
export type TTenderResponse = z.infer<typeof TendersResponse>;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { z } from "zod";
|
||||
import { createApiResponseSchema } from "./Factory";
|
||||
|
||||
export const UserSchema = z.object({
|
||||
id: z.string().regex(/^[0-9a-fA-F]{24}$/, {
|
||||
message: "Invalid ObjectId",
|
||||
}),
|
||||
full_name: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
role: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
company_id: z.string().optional(),
|
||||
department: z.string().optional(),
|
||||
position: z.string().optional(),
|
||||
phone: z.string().optional(),
|
||||
profile_image: z.string().or(z.null()).optional(),
|
||||
is_verified: z.boolean().optional(),
|
||||
last_login_at: z.number().or(z.null()).optional(),
|
||||
created_at: z.number().optional(),
|
||||
updated_at: z.number().optional(),
|
||||
updated_by: z.string().optional(),
|
||||
});
|
||||
|
||||
export const CreateAdminSchema = z.object({
|
||||
full_name: z.string().min(1, "Full name is required"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters long")
|
||||
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
|
||||
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
|
||||
.regex(/\d/, "Password must contain at least one number")
|
||||
.regex(/[\W_]/, "Password must contain at least one special character"),
|
||||
|
||||
profile_image: z.string().optional(),
|
||||
|
||||
department: z.string().min(1, "Department is required"),
|
||||
position: z.string().min(1, "Position is required"),
|
||||
role: z.enum(["manager", "employee", "admin", "guest"]),
|
||||
username: z.string().min(3, "Username must be at least 3 characters long"),
|
||||
});
|
||||
|
||||
export const LoginCredentials = z.object({
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
export const AdminListResponse = z.object({
|
||||
users: z.array(UserSchema),
|
||||
});
|
||||
export const LoginResponse = z.object({
|
||||
access_token: z.string(),
|
||||
refresh_token: z.string(),
|
||||
expires_at: z.number(),
|
||||
user: UserSchema,
|
||||
});
|
||||
export const LogoutResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
export type IUser = z.infer<typeof UserSchema>;
|
||||
export type ILogoutResponse = z.infer<typeof LogoutResponse>;
|
||||
export type ILoginCredentials = z.infer<typeof LoginCredentials>;
|
||||
export type ILoginResponse = z.infer<typeof LoginResponse>;
|
||||
export type IAdminListResponse = z.infer<typeof AdminListResponse>;
|
||||
|
||||
export const LogoutResponseSchema = createApiResponseSchema(LogoutResponse);
|
||||
export const LoginResponseSchema = createApiResponseSchema(LoginResponse);
|
||||
export const AdminListResponseSchema =
|
||||
createApiResponseSchema(AdminListResponse);
|
||||
export type ICreateAdminCredentials = z.infer<typeof CreateAdminSchema>;
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from "./shared";
|
||||
export * from "./User";
|
||||
export * from "./Posts";
|
||||
export * from "./Factory";
|
||||
export * from "./TCompany";
|
||||
export * from "./Customers";
|
||||
export * from "./Profile";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IMetaData } from "./Factory";
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
data: T;
|
||||
message: string;
|
||||
code?: number;
|
||||
meta?: IMetaData;
|
||||
success: boolean;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
export function formatMessageTime(timestamp: string) {
|
||||
const messageDate = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffInMinutes = Math.floor(
|
||||
(now.getTime() - messageDate.getTime()) / (60 * 1000),
|
||||
);
|
||||
const diffInHours = Math.floor(diffInMinutes / 60);
|
||||
const diffInDays = Math.floor(diffInHours / 24);
|
||||
|
||||
// For messages from today, show time
|
||||
if (diffInDays === 0) {
|
||||
// If less than 60 minutes ago, show "X min"
|
||||
if (diffInMinutes < 60) {
|
||||
return diffInMinutes === 0 ? "just now" : `${diffInMinutes}m`;
|
||||
}
|
||||
// Otherwise show time like "4:39 PM"
|
||||
return messageDate.toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
}
|
||||
|
||||
// For messages from this week, show day name
|
||||
if (diffInDays < 7) {
|
||||
return messageDate.toLocaleDateString("en-US", { weekday: "long" });
|
||||
}
|
||||
|
||||
// For messages from this year, show date
|
||||
if (messageDate.getFullYear() === now.getFullYear()) {
|
||||
return messageDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
});
|
||||
}
|
||||
|
||||
// For older messages, show date with year
|
||||
return messageDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export function compactFormat(value: number) {
|
||||
const formatter = new Intl.NumberFormat("en", {
|
||||
notation: "compact",
|
||||
compactDisplay: "short",
|
||||
});
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
export function standardFormat(value: number) {
|
||||
return value.toLocaleString("en-US", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const _COOKIE_PREFIX = "TENDER_PANEL";
|
||||
|
||||
export const COOKIE_KEYS = {
|
||||
locale: `${_COOKIE_PREFIX}_locale`,
|
||||
access_token: `${_COOKIE_PREFIX}_token`,
|
||||
theme: `${_COOKIE_PREFIX}_theme`,
|
||||
refresh_token: `${_COOKIE_PREFIX}_refresh_token`,
|
||||
user: `${_COOKIE_PREFIX}_user`,
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user