init
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@garage/api",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./infra": "./src/infra/index.ts",
|
||||
"./clients": "./src/clients/index.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./postman/*": "./postman/*",
|
||||
"./open-api/*": "./open-api/*",
|
||||
"./types": "./types/index.ts",
|
||||
"./types/*": "./types/*"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare:dirs": "node -e \"const fs=require('fs');fs.mkdirSync('open-api',{recursive:true});fs.mkdirSync('types',{recursive:true});\"",
|
||||
"generate:openapi": "pnpm run prepare:dirs && node scripts/generate-openapi.cjs",
|
||||
"generate:types": "node scripts/generate-types.cjs",
|
||||
"generate": "pnpm run generate:openapi && pnpm run generate:types",
|
||||
"dev": "pnpm run generate",
|
||||
"build": "pnpm run generate",
|
||||
"lint": "echo \"No lint configured for @garage/api\"",
|
||||
"check-types": "echo \"No typecheck configured for @garage/api\""
|
||||
},
|
||||
"dependencies": {
|
||||
"openapi-fetch": "^0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"openapi-typescript": "^7.10.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": ">=14",
|
||||
"server-only": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"next": { "optional": true },
|
||||
"server-only": { "optional": true }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,223 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const collectionPath = process.argv[2] || "postman/collection.json";
|
||||
const outputPath = "open-api/schema.json";
|
||||
|
||||
// ── Schema inference from JSON examples ─────────────────────────────
|
||||
|
||||
function inferSchema(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return { type: "string", nullable: true };
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return { type: "boolean" };
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return Number.isInteger(value) ? { type: "integer" } : { type: "number" };
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
||||
return { type: "string", format: "date-time" };
|
||||
}
|
||||
return { type: "string" };
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return { type: "array", items: {} };
|
||||
}
|
||||
return { type: "array", items: inferSchema(value[0]) };
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const properties = {};
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
properties[key] = inferSchema(val);
|
||||
}
|
||||
return { type: "object", properties };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ── Path helpers ────────────────────────────────────────────────────
|
||||
|
||||
function extractPath(url) {
|
||||
const parts = url.path || [];
|
||||
const raw = "/" + parts.join("/");
|
||||
return raw.replace(/\{\{(\w+)\}\}/g, "{$1}");
|
||||
}
|
||||
|
||||
function extractPathParams(apiPath) {
|
||||
const params = [];
|
||||
const re = /\{(\w+)\}/g;
|
||||
let m;
|
||||
while ((m = re.exec(apiPath)) !== null) {
|
||||
params.push({
|
||||
name: m[1],
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
});
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// ── Request body ────────────────────────────────────────────────────
|
||||
|
||||
function buildRequestBody(body) {
|
||||
if (!body) return undefined;
|
||||
|
||||
if (body.mode === "raw" && body.raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(body.raw);
|
||||
return {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: inferSchema(parsed),
|
||||
example: parsed,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
content: {
|
||||
"text/plain": { schema: { type: "string" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (body.mode === "formdata" && body.formdata) {
|
||||
const properties = {};
|
||||
for (const field of body.formdata) {
|
||||
properties[field.key] =
|
||||
field.type === "file"
|
||||
? { type: "string", format: "binary" }
|
||||
: { type: "string" };
|
||||
}
|
||||
return {
|
||||
content: {
|
||||
"multipart/form-data": {
|
||||
schema: { type: "object", properties },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ── Response schemas ────────────────────────────────────────────────
|
||||
|
||||
function buildResponses(responses) {
|
||||
const out = {};
|
||||
|
||||
if (!responses || responses.length === 0) {
|
||||
out["200"] = { description: "OK" };
|
||||
return out;
|
||||
}
|
||||
|
||||
for (const resp of responses) {
|
||||
const code = String(resp.code || 200);
|
||||
const desc = resp.status || "OK";
|
||||
const entry = { description: desc };
|
||||
|
||||
if (resp.body) {
|
||||
try {
|
||||
const parsed = JSON.parse(resp.body);
|
||||
entry.content = {
|
||||
"application/json": {
|
||||
schema: inferSchema(parsed),
|
||||
example: parsed,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
entry.content = {
|
||||
"text/plain": { schema: { type: "string" } },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
out[code] = entry;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Tree walker ─────────────────────────────────────────────────────
|
||||
|
||||
function processItem(item, tag, paths) {
|
||||
const req = item.request;
|
||||
if (!req) return;
|
||||
|
||||
const method = req.method.toLowerCase();
|
||||
const apiPath = extractPath(req.url);
|
||||
const pathParams = extractPathParams(apiPath);
|
||||
|
||||
if (!paths[apiPath]) paths[apiPath] = {};
|
||||
|
||||
const operation = {
|
||||
tags: [tag],
|
||||
summary: item.name,
|
||||
};
|
||||
|
||||
const reqBody = buildRequestBody(req.body);
|
||||
if (reqBody) operation.requestBody = reqBody;
|
||||
|
||||
if (pathParams.length > 0) operation.parameters = pathParams;
|
||||
|
||||
operation.responses = buildResponses(item.response);
|
||||
|
||||
paths[apiPath][method] = operation;
|
||||
}
|
||||
|
||||
function walkFolder(folder, paths, tag) {
|
||||
const currentTag = folder.name || tag;
|
||||
if (!folder.item) return;
|
||||
|
||||
for (const child of folder.item) {
|
||||
if (child.item) {
|
||||
walkFolder(child, paths, currentTag);
|
||||
} else {
|
||||
processItem(child, currentTag, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main ────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
const collection = JSON.parse(fs.readFileSync(collectionPath, "utf-8"));
|
||||
|
||||
const tags = new Set();
|
||||
const paths = {};
|
||||
|
||||
for (const folder of collection.item) {
|
||||
tags.add(folder.name);
|
||||
walkFolder(folder, paths, folder.name);
|
||||
}
|
||||
|
||||
const spec = {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: collection.info.name || "API",
|
||||
description: collection.info.description || "",
|
||||
version: "1.0.0",
|
||||
},
|
||||
servers: [{ url: "http://{{base_url}}" }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: { type: "http", scheme: "bearer" },
|
||||
},
|
||||
},
|
||||
security: [{ bearerAuth: [] }],
|
||||
tags: Array.from(tags).map((name) => ({ name })),
|
||||
paths,
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
|
||||
console.log(`OpenAPI schema written to ${outputPath}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,13 @@
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const schemaSource = process.argv[2] || "open-api/schema.json";
|
||||
const outputPath = "types/index.ts";
|
||||
|
||||
try {
|
||||
execSync(`npx openapi-typescript ${schemaSource} -o ${outputPath}`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to generate TypeScript types from OpenAPI schema.");
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { ApiClientOptions } from "./infra/client"
|
||||
import { AuthClient } from "./clients/auth"
|
||||
import { CustomersClient } from "./clients/customers"
|
||||
import { ReferralSourcesClient } from "./clients/referral-sources"
|
||||
import { VehiclesClient } from "./clients/vehicles"
|
||||
import { VehicleAttributesClient } from "./clients/vehicle-attributes"
|
||||
import { VehicleDocumentsClient } from "./clients/vehicle-documents"
|
||||
import { DepartmentsClient } from "./clients/departments"
|
||||
import { EmployeesClient } from "./clients/employees"
|
||||
import { GeoClient } from "./clients/geo"
|
||||
import { PaymentTermsClient } from "./clients/payment-terms"
|
||||
import { ShopTypesClient } from "./clients/shop-types"
|
||||
import { InventoryClient } from "./clients/inventory"
|
||||
import { VendorsClient } from "./clients/vendors"
|
||||
import { InspectionsClient } from "./clients/inspections"
|
||||
import { LabelsClient } from "./clients/labels"
|
||||
import { InsuranceTypesClient } from "./clients/insurance-types"
|
||||
import { EstimatesClient } from "./clients/estimates"
|
||||
import { JobCardsClient } from "./clients/job-cards"
|
||||
import { PaymentsClient } from "./clients/payments"
|
||||
import { PartsClient } from "./clients/parts"
|
||||
import { PurchaseOrdersClient } from "./clients/purchase-orders"
|
||||
import { ServicesClient } from "./clients/services"
|
||||
import { ServiceGroupsClient } from "./clients/service-groups"
|
||||
import { ExpensesClient } from "./clients/expenses"
|
||||
import { TasksClient } from "./clients/tasks"
|
||||
import { AppointmentsClient } from "./clients/appointments"
|
||||
import { ShopTimingsClient } from "./clients/shop-timings"
|
||||
import { ShopCalendarsClient } from "./clients/shop-calendars"
|
||||
|
||||
export function createApi(options?: ApiClientOptions) {
|
||||
return {
|
||||
auth: new AuthClient(undefined, options),
|
||||
customers: new CustomersClient(undefined, options),
|
||||
referralSources: new ReferralSourcesClient(undefined, options),
|
||||
vehicles: new VehiclesClient(undefined, options),
|
||||
vehicleAttributes: new VehicleAttributesClient(undefined, options),
|
||||
vehicleDocuments: new VehicleDocumentsClient(undefined, options),
|
||||
departments: new DepartmentsClient(undefined, options),
|
||||
employees: new EmployeesClient(undefined, options),
|
||||
geo: new GeoClient(undefined, options),
|
||||
paymentTerms: new PaymentTermsClient(undefined, options),
|
||||
shopTypes: new ShopTypesClient(undefined, options),
|
||||
inventory: new InventoryClient(undefined, options),
|
||||
vendors: new VendorsClient(undefined, options),
|
||||
inspections: new InspectionsClient(undefined, options),
|
||||
labels: new LabelsClient(undefined, options),
|
||||
insuranceTypes: new InsuranceTypesClient(undefined, options),
|
||||
estimates: new EstimatesClient(undefined, options),
|
||||
jobCards: new JobCardsClient(undefined, options),
|
||||
payments: new PaymentsClient(undefined, options),
|
||||
parts: new PartsClient(undefined, options),
|
||||
purchaseOrders: new PurchaseOrdersClient(undefined, options),
|
||||
services: new ServicesClient(undefined, options),
|
||||
serviceGroups: new ServiceGroupsClient(undefined, options),
|
||||
expenses: new ExpensesClient(undefined, options),
|
||||
tasks: new TasksClient(undefined, options),
|
||||
appointments: new AppointmentsClient(undefined, options),
|
||||
shopTimings: new ShopTimingsClient(undefined, options),
|
||||
shopCalendars: new ShopCalendarsClient(undefined, options),
|
||||
}
|
||||
}
|
||||
|
||||
/** Unauthenticated singleton — use for public calls (login, register) */
|
||||
export const api = createApi()
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const APPOINTMENT_ROUTES = {
|
||||
INDEX: "/api/appointments",
|
||||
BY_ID: "/api/appointments/{id}",
|
||||
UNLINK_JOB_CARD: "/api/appointments/{id}/un-link-job-card",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class AppointmentsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(APPOINTMENT_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof APPOINTMENT_ROUTES.INDEX, "post">) {
|
||||
return this.post(APPOINTMENT_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof APPOINTMENT_ROUTES.BY_ID, "put">) {
|
||||
return this.put(APPOINTMENT_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(APPOINTMENT_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async unlinkJobCard(id: string, payload: ApiRequestBody<typeof APPOINTMENT_ROUTES.UNLINK_JOB_CARD, "post">) {
|
||||
return this.post(APPOINTMENT_ROUTES.UNLINK_JOB_CARD, payload, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
|
||||
export const AUTH_ROUTES = {
|
||||
LOGIN: "/api/login",
|
||||
PROFILE: "/api/profile",
|
||||
LOGOUT: "/api/logout",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class AuthClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async login(payload: ApiRequestBody<typeof AUTH_ROUTES.LOGIN, "post">) {
|
||||
return this.post(AUTH_ROUTES.LOGIN, payload)
|
||||
}
|
||||
|
||||
async profile() {
|
||||
return this.get(AUTH_ROUTES.PROFILE)
|
||||
}
|
||||
|
||||
async logout() {
|
||||
return this.post(AUTH_ROUTES.LOGOUT, undefined)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { CrudClient } from "../infra/crud-client"
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const CUSTOMER_ROUTES = {
|
||||
INDEX: "/api/customers",
|
||||
BY_ID: "/api/customers/{id}",
|
||||
EXPORT: "/api/customers/export",
|
||||
IMPORT: "/api/customers/import",
|
||||
CUSTOMER_TYPES: "/api/customer-types",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class CustomersClient extends CrudClient<typeof CUSTOMER_ROUTES.INDEX, typeof CUSTOMER_ROUTES.BY_ID> {
|
||||
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions, CUSTOMER_ROUTES.INDEX, CUSTOMER_ROUTES.BY_ID)
|
||||
|
||||
}
|
||||
|
||||
async listCustomerTypes(query?: ApiListQueryParams) {
|
||||
return this.get(CUSTOMER_ROUTES.CUSTOMER_TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async export() {
|
||||
return this.get(CUSTOMER_ROUTES.EXPORT)
|
||||
}
|
||||
|
||||
async import(payload: ApiRequestBody<typeof CUSTOMER_ROUTES.IMPORT, "post">) {
|
||||
return this.post(CUSTOMER_ROUTES.IMPORT, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const DEPARTMENT_ROUTES = {
|
||||
INDEX: "/api/departments",
|
||||
BY_ID: "/api/departments/{id}",
|
||||
SET_FAVORITE: "/api/set-favorite-department",
|
||||
REMOVE_FAVORITE: "/api/remove-favorite-department",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class DepartmentsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(DEPARTMENT_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof DEPARTMENT_ROUTES.INDEX, "post">) {
|
||||
return this.post(DEPARTMENT_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof DEPARTMENT_ROUTES.BY_ID, "put">) {
|
||||
return this.put(DEPARTMENT_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(DEPARTMENT_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setFavorite(payload: ApiRequestBody<typeof DEPARTMENT_ROUTES.SET_FAVORITE, "post">) {
|
||||
return this.post(DEPARTMENT_ROUTES.SET_FAVORITE, payload)
|
||||
}
|
||||
|
||||
async removeFavorite(payload: ApiRequestBody<typeof DEPARTMENT_ROUTES.REMOVE_FAVORITE, "post">) {
|
||||
return this.post(DEPARTMENT_ROUTES.REMOVE_FAVORITE, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { CrudClient } from "../infra/crud-client"
|
||||
import type { ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath } from "../infra/types"
|
||||
|
||||
export const EMPLOYEE_ROUTES = {
|
||||
INDEX: "/api/employees",
|
||||
BY_ID: "/api/employees/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class EmployeesClient extends CrudClient<
|
||||
typeof EMPLOYEE_ROUTES.INDEX,
|
||||
typeof EMPLOYEE_ROUTES.BY_ID
|
||||
> {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions, EMPLOYEE_ROUTES.INDEX, EMPLOYEE_ROUTES.BY_ID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const ESTIMATE_ROUTES = {
|
||||
INDEX: "/api/estimates",
|
||||
BY_ID: "/api/estimates/{id}",
|
||||
QUICK_REMARKS: "/api/quick-remark",
|
||||
QUICK_REMARK_BY_ID: "/api/quick-remark/{id}",
|
||||
QUICK_NOTES: "/api/quick-notes",
|
||||
QUICK_NOTE_BY_ID: "/api/quick-notes/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class EstimatesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Estimates ──
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(ESTIMATE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof ESTIMATE_ROUTES.INDEX, "post">) {
|
||||
return this.post(ESTIMATE_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof ESTIMATE_ROUTES.BY_ID, "put">) {
|
||||
return this.put(ESTIMATE_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(ESTIMATE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Quick Remarks ──
|
||||
async listQuickRemarks(query?: ApiListQueryParams) {
|
||||
return this.get(ESTIMATE_ROUTES.QUICK_REMARKS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createQuickRemark(payload: ApiRequestBody<typeof ESTIMATE_ROUTES.QUICK_REMARKS, "post">) {
|
||||
return this.post(ESTIMATE_ROUTES.QUICK_REMARKS, payload)
|
||||
}
|
||||
|
||||
async updateQuickRemark(id: string, payload: ApiRequestBody<typeof ESTIMATE_ROUTES.QUICK_REMARK_BY_ID, "put">) {
|
||||
return this.put(ESTIMATE_ROUTES.QUICK_REMARK_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyQuickRemark(id: string) {
|
||||
return this.delete(ESTIMATE_ROUTES.QUICK_REMARK_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Quick Notes ──
|
||||
async listQuickNotes(query?: ApiListQueryParams) {
|
||||
return this.get(ESTIMATE_ROUTES.QUICK_NOTES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createQuickNote(payload: ApiRequestBody<typeof ESTIMATE_ROUTES.QUICK_NOTES, "post">) {
|
||||
return this.post(ESTIMATE_ROUTES.QUICK_NOTES, payload)
|
||||
}
|
||||
|
||||
async updateQuickNote(id: string, payload: ApiRequestBody<typeof ESTIMATE_ROUTES.QUICK_NOTE_BY_ID, "put">) {
|
||||
return this.put(ESTIMATE_ROUTES.QUICK_NOTE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyQuickNote(id: string) {
|
||||
return this.delete(ESTIMATE_ROUTES.QUICK_NOTE_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const EXPENSE_ROUTES = {
|
||||
ITEMS: "/api/expense-items",
|
||||
ITEM_BY_ID: "/api/expense-items/{id}",
|
||||
TOGGLE_ITEM_STATUS: "/api/toggle-expense-item-status",
|
||||
BILLS: "/api/bills",
|
||||
BILL_BY_ID: "/api/bills/{id}",
|
||||
EXPENSES: "/api/expenses",
|
||||
EXPENSE_BY_ID: "/api/expenses/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ExpensesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Expense Items ──
|
||||
async listItems(query?: ApiListQueryParams) {
|
||||
return this.get(EXPENSE_ROUTES.ITEMS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createItem(payload: ApiRequestBody<typeof EXPENSE_ROUTES.ITEMS, "post">) {
|
||||
return this.post(EXPENSE_ROUTES.ITEMS, payload)
|
||||
}
|
||||
|
||||
async updateItem(id: string, payload: ApiRequestBody<typeof EXPENSE_ROUTES.ITEM_BY_ID, "put">) {
|
||||
return this.put(EXPENSE_ROUTES.ITEM_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyItem(id: string) {
|
||||
return this.delete(EXPENSE_ROUTES.ITEM_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async toggleItemStatus(payload: ApiRequestBody<typeof EXPENSE_ROUTES.TOGGLE_ITEM_STATUS, "post">) {
|
||||
return this.post(EXPENSE_ROUTES.TOGGLE_ITEM_STATUS, payload)
|
||||
}
|
||||
|
||||
// ── Bills ──
|
||||
async listBills(query?: ApiListQueryParams) {
|
||||
return this.get(EXPENSE_ROUTES.BILLS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createBill(payload: ApiRequestBody<typeof EXPENSE_ROUTES.BILLS, "post">) {
|
||||
return this.post(EXPENSE_ROUTES.BILLS, payload)
|
||||
}
|
||||
|
||||
async updateBill(id: string, payload: ApiRequestBody<typeof EXPENSE_ROUTES.BILL_BY_ID, "put">) {
|
||||
return this.put(EXPENSE_ROUTES.BILL_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyBill(id: string) {
|
||||
return this.delete(EXPENSE_ROUTES.BILL_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Expenses ──
|
||||
async listExpenses(query?: ApiListQueryParams) {
|
||||
return this.get(EXPENSE_ROUTES.EXPENSES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createExpense(payload: ApiRequestBody<typeof EXPENSE_ROUTES.EXPENSES, "post">) {
|
||||
return this.post(EXPENSE_ROUTES.EXPENSES, payload)
|
||||
}
|
||||
|
||||
async updateExpense(id: string, payload: ApiRequestBody<typeof EXPENSE_ROUTES.EXPENSE_BY_ID, "put">) {
|
||||
return this.put(EXPENSE_ROUTES.EXPENSE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyExpense(id: string) {
|
||||
return this.delete(EXPENSE_ROUTES.EXPENSE_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath } from "../infra/types"
|
||||
|
||||
export const GEO_ROUTES = {
|
||||
COUNTRIES: "/api/countries",
|
||||
STATES: "/api/states",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class GeoClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async countries() {
|
||||
return this.get(GEO_ROUTES.COUNTRIES)
|
||||
}
|
||||
|
||||
async states() {
|
||||
return this.get(GEO_ROUTES.STATES)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export { AuthClient, AUTH_ROUTES } from "./auth"
|
||||
export { CustomersClient, CUSTOMER_ROUTES } from "./customers"
|
||||
export { ReferralSourcesClient, REFERRAL_SOURCE_ROUTES } from "./referral-sources"
|
||||
export { VehiclesClient, VEHICLE_ROUTES } from "./vehicles"
|
||||
export { VehicleAttributesClient, VEHICLE_ATTRIBUTE_ROUTES } from "./vehicle-attributes"
|
||||
export { VehicleDocumentsClient, VEHICLE_DOCUMENT_ROUTES } from "./vehicle-documents"
|
||||
export { DepartmentsClient, DEPARTMENT_ROUTES } from "./departments"
|
||||
export { EmployeesClient, EMPLOYEE_ROUTES } from "./employees"
|
||||
export { GeoClient, GEO_ROUTES } from "./geo"
|
||||
export { PaymentTermsClient, PAYMENT_TERM_ROUTES } from "./payment-terms"
|
||||
export { ShopTypesClient, SHOP_TYPE_ROUTES, type ShopTypeCreatePayload, type ShopTypeUpdatePayload } from "./shop-types"
|
||||
export { InventoryClient, INVENTORY_ROUTES } from "./inventory"
|
||||
export { VendorsClient, VENDOR_ROUTES } from "./vendors"
|
||||
export { InspectionsClient, INSPECTION_ROUTES } from "./inspections"
|
||||
export { LabelsClient, LABEL_ROUTES } from "./labels"
|
||||
export { InsuranceTypesClient, INSURANCE_TYPE_ROUTES } from "./insurance-types"
|
||||
export { EstimatesClient, ESTIMATE_ROUTES } from "./estimates"
|
||||
export { JobCardsClient, JOB_CARD_ROUTES } from "./job-cards"
|
||||
export { PaymentsClient, PAYMENT_ROUTES } from "./payments"
|
||||
export { PartsClient, PARTS_ROUTES } from "./parts"
|
||||
export { PurchaseOrdersClient, PURCHASE_ORDER_ROUTES } from "./purchase-orders"
|
||||
export { ServicesClient, SERVICE_ROUTES } from "./services"
|
||||
export { ServiceGroupsClient, SERVICE_GROUP_ROUTES } from "./service-groups"
|
||||
export { ExpensesClient, EXPENSE_ROUTES } from "./expenses"
|
||||
export { TasksClient, TASK_ROUTES } from "./tasks"
|
||||
export { AppointmentsClient, APPOINTMENT_ROUTES } from "./appointments"
|
||||
export { ShopTimingsClient, SHOP_TIMING_ROUTES } from "./shop-timings"
|
||||
export { ShopCalendarsClient, SHOP_CALENDAR_ROUTES } from "./shop-calendars"
|
||||
@@ -0,0 +1,118 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const INSPECTION_ROUTES = {
|
||||
CATEGORIES: "/api/inspection-categories",
|
||||
CATEGORY_BY_ID: "/api/inspection-categories/{id}",
|
||||
INDEX: "/api/inspections",
|
||||
BY_ID: "/api/inspections/{id}",
|
||||
CHANGE_STATUS: "/api/change-inspection-status",
|
||||
CHECKPOINT_LABELS: "/api/check-point-label",
|
||||
CHECKPOINT_LABEL_BY_ID: "/api/check-point-label/{id}",
|
||||
CHECKPOINTS: "/api/inspection-check-points",
|
||||
CHECKPOINT_BY_ID: "/api/inspection-check-points/{id}",
|
||||
TOGGLE_LABEL_TO_CHECKPOINT: "/api/toggle-label-to-checkpoint",
|
||||
CHECKPOINT_CHANGE_STATUS: "/api/inspection-check-points/change-status",
|
||||
CHECKPOINT_ADD_ATTACHMENT: "/api/inspection-check-points/add-attachment",
|
||||
CHECKPOINT_UPLOAD_MEDIA: "/api/inspection-check-points/{id}/upload-media",
|
||||
CHECKPOINT_MEDIA: "/api/inspection-check-points/{id}/media",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class InspectionsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Categories ──
|
||||
async listCategories(query?: ApiListQueryParams) {
|
||||
return this.get(INSPECTION_ROUTES.CATEGORIES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createCategory(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CATEGORIES, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CATEGORIES, payload)
|
||||
}
|
||||
|
||||
async updateCategory(id: string, payload: ApiRequestBody<typeof INSPECTION_ROUTES.CATEGORY_BY_ID, "put">) {
|
||||
return this.put(INSPECTION_ROUTES.CATEGORY_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyCategory(id: string) {
|
||||
return this.delete(INSPECTION_ROUTES.CATEGORY_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Inspections ──
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(INSPECTION_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof INSPECTION_ROUTES.INDEX, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof INSPECTION_ROUTES.BY_ID, "put">) {
|
||||
return this.put(INSPECTION_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(INSPECTION_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async changeStatus(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHANGE_STATUS, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHANGE_STATUS, payload)
|
||||
}
|
||||
|
||||
// ── Checkpoint Labels ──
|
||||
async listCheckpointLabels(query?: ApiListQueryParams) {
|
||||
return this.get(INSPECTION_ROUTES.CHECKPOINT_LABELS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createCheckpointLabel(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_LABELS, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHECKPOINT_LABELS, payload)
|
||||
}
|
||||
|
||||
async updateCheckpointLabel(id: string, payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_LABEL_BY_ID, "put">) {
|
||||
return this.put(INSPECTION_ROUTES.CHECKPOINT_LABEL_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyCheckpointLabel(id: string) {
|
||||
return this.delete(INSPECTION_ROUTES.CHECKPOINT_LABEL_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Checkpoints ──
|
||||
async listCheckpoints(query?: ApiListQueryParams) {
|
||||
return this.get(INSPECTION_ROUTES.CHECKPOINTS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createCheckpoint(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINTS, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHECKPOINTS, payload)
|
||||
}
|
||||
|
||||
async updateCheckpoint(id: string, payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_BY_ID, "put">) {
|
||||
return this.put(INSPECTION_ROUTES.CHECKPOINT_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyCheckpoint(id: string) {
|
||||
return this.delete(INSPECTION_ROUTES.CHECKPOINT_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async toggleLabelToCheckpoint(payload: ApiRequestBody<typeof INSPECTION_ROUTES.TOGGLE_LABEL_TO_CHECKPOINT, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.TOGGLE_LABEL_TO_CHECKPOINT, payload)
|
||||
}
|
||||
|
||||
async changeCheckpointStatus(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_CHANGE_STATUS, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHECKPOINT_CHANGE_STATUS, payload)
|
||||
}
|
||||
|
||||
async addCheckpointAttachment(payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_ADD_ATTACHMENT, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHECKPOINT_ADD_ATTACHMENT, payload)
|
||||
}
|
||||
|
||||
async uploadCheckpointMedia(id: string, payload: ApiRequestBody<typeof INSPECTION_ROUTES.CHECKPOINT_UPLOAD_MEDIA, "post">) {
|
||||
return this.post(INSPECTION_ROUTES.CHECKPOINT_UPLOAD_MEDIA, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async deleteCheckpointMedia(id: string) {
|
||||
return this.delete(INSPECTION_ROUTES.CHECKPOINT_MEDIA, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const INSURANCE_TYPE_ROUTES = {
|
||||
INDEX: "/api/insurance-types",
|
||||
BY_ID: "/api/insurance-types/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class InsuranceTypesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(INSURANCE_TYPE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof INSURANCE_TYPE_ROUTES.INDEX, "post">) {
|
||||
return this.post(INSURANCE_TYPE_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof INSURANCE_TYPE_ROUTES.BY_ID, "put">) {
|
||||
return this.put(INSURANCE_TYPE_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(INSURANCE_TYPE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const INVENTORY_ROUTES = {
|
||||
UNIT_TYPES: "/api/unit-types",
|
||||
UNIT_TYPE_BY_ID: "/api/unit-types/{id}",
|
||||
SET_FAVORITE_UNIT_TYPE: "/api/set-favorite-unit-type",
|
||||
REMOVE_FAVORITE_UNIT_TYPE: "/api/remove-favorite-unit-type",
|
||||
CATEGORIES: "/api/inventory-categories",
|
||||
CATEGORY_BY_ID: "/api/inventory-categories/{id}",
|
||||
SET_FAVORITE_CATEGORY: "/api/set-favorite-inventory-category",
|
||||
REMOVE_FAVORITE_CATEGORY: "/api/remove-favorite-inventory-category",
|
||||
LABOR_RATES: "/api/labor-rates",
|
||||
LABOR_RATE_BY_ID: "/api/labor-rates/{id}",
|
||||
SET_FAVORITE_LABOR_RATE: "/api/set-favorite-labor-rate",
|
||||
REMOVE_FAVORITE_LABOR_RATE: "/api/remove-favorite-labor-rate",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class InventoryClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Unit Types ──
|
||||
async listUnitTypes(query?: ApiListQueryParams) {
|
||||
return this.get(INVENTORY_ROUTES.UNIT_TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createUnitType(payload: ApiRequestBody<typeof INVENTORY_ROUTES.UNIT_TYPES, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.UNIT_TYPES, payload)
|
||||
}
|
||||
|
||||
async updateUnitType(id: string, payload: ApiRequestBody<typeof INVENTORY_ROUTES.UNIT_TYPE_BY_ID, "put">) {
|
||||
return this.put(INVENTORY_ROUTES.UNIT_TYPE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyUnitType(id: string) {
|
||||
return this.delete(INVENTORY_ROUTES.UNIT_TYPE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setFavoriteUnitType(payload: ApiRequestBody<typeof INVENTORY_ROUTES.SET_FAVORITE_UNIT_TYPE, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.SET_FAVORITE_UNIT_TYPE, payload)
|
||||
}
|
||||
|
||||
async removeFavoriteUnitType(payload: ApiRequestBody<typeof INVENTORY_ROUTES.REMOVE_FAVORITE_UNIT_TYPE, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.REMOVE_FAVORITE_UNIT_TYPE, payload)
|
||||
}
|
||||
|
||||
// ── Inventory Categories ──
|
||||
async listCategories(query?: ApiListQueryParams) {
|
||||
return this.get(INVENTORY_ROUTES.CATEGORIES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createCategory(payload: ApiRequestBody<typeof INVENTORY_ROUTES.CATEGORIES, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.CATEGORIES, payload)
|
||||
}
|
||||
|
||||
async updateCategory(id: string, payload: ApiRequestBody<typeof INVENTORY_ROUTES.CATEGORY_BY_ID, "put">) {
|
||||
return this.put(INVENTORY_ROUTES.CATEGORY_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyCategory(id: string) {
|
||||
return this.delete(INVENTORY_ROUTES.CATEGORY_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setFavoriteCategory(payload: ApiRequestBody<typeof INVENTORY_ROUTES.SET_FAVORITE_CATEGORY, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.SET_FAVORITE_CATEGORY, payload)
|
||||
}
|
||||
|
||||
async removeFavoriteCategory(payload: ApiRequestBody<typeof INVENTORY_ROUTES.REMOVE_FAVORITE_CATEGORY, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.REMOVE_FAVORITE_CATEGORY, payload)
|
||||
}
|
||||
|
||||
// ── Labor Rates ──
|
||||
async listLaborRates(query?: ApiListQueryParams) {
|
||||
return this.get(INVENTORY_ROUTES.LABOR_RATES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createLaborRate(payload: ApiRequestBody<typeof INVENTORY_ROUTES.LABOR_RATES, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.LABOR_RATES, payload)
|
||||
}
|
||||
|
||||
async updateLaborRate(id: string, payload: ApiRequestBody<typeof INVENTORY_ROUTES.LABOR_RATE_BY_ID, "put">) {
|
||||
return this.put(INVENTORY_ROUTES.LABOR_RATE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyLaborRate(id: string) {
|
||||
return this.delete(INVENTORY_ROUTES.LABOR_RATE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setFavoriteLaborRate(payload: ApiRequestBody<typeof INVENTORY_ROUTES.SET_FAVORITE_LABOR_RATE, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.SET_FAVORITE_LABOR_RATE, payload)
|
||||
}
|
||||
|
||||
async removeFavoriteLaborRate(payload: ApiRequestBody<typeof INVENTORY_ROUTES.REMOVE_FAVORITE_LABOR_RATE, "post">) {
|
||||
return this.post(INVENTORY_ROUTES.REMOVE_FAVORITE_LABOR_RATE, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const JOB_CARD_ROUTES = {
|
||||
INDEX: "/api/job-cards",
|
||||
BY_ID: "/api/job-cards/{id}",
|
||||
CHANGE_DATE: "/api/job-cards/{id}/change-date",
|
||||
CHANGE_STATUS: "/api/job-cards/{id}/change-status",
|
||||
ADD_CUSTOMER_REMARK: "/api/job-cards/{id}/add-customer-remark",
|
||||
EDIT_CUSTOMER_REMARK: "/api/job-cards/{id}/edit-customer-remark",
|
||||
DELETE_CUSTOMER_REMARK: "/api/job-cards/{id}/delete-customer-remark",
|
||||
ADD_SHOP_RECOMMENDATION: "/api/job-cards/{id}/add-shop-recommendation",
|
||||
EDIT_SHOP_RECOMMENDATION: "/api/job-cards/{id}/edit-shop-recommendation",
|
||||
DELETE_SHOP_RECOMMENDATION: "/api/job-cards/{id}/delete-shop-recommendation",
|
||||
ADD_ATTACHMENT: "/api/job-cards/{id}/add-attachment",
|
||||
DELETE_ATTACHMENT: "/api/job-cards/{id}/delete-attachment",
|
||||
CHANGE_SERVICE_WRITER: "/api/job-cards/{id}/change-service-writer-id",
|
||||
CHANGE_SALES_PERSON: "/api/job-cards/{id}/change-sales-person-id",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class JobCardsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(JOB_CARD_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof JOB_CARD_ROUTES.INDEX, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.BY_ID, "put">) {
|
||||
return this.put(JOB_CARD_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(JOB_CARD_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async changeDate(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.CHANGE_DATE, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.CHANGE_DATE, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async changeStatus(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.CHANGE_STATUS, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.CHANGE_STATUS, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async addCustomerRemark(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.ADD_CUSTOMER_REMARK, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.ADD_CUSTOMER_REMARK, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async editCustomerRemark(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.EDIT_CUSTOMER_REMARK, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.EDIT_CUSTOMER_REMARK, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async deleteCustomerRemark(id: string) {
|
||||
return this.delete(JOB_CARD_ROUTES.DELETE_CUSTOMER_REMARK, { params: { id } })
|
||||
}
|
||||
|
||||
async addShopRecommendation(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.ADD_SHOP_RECOMMENDATION, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.ADD_SHOP_RECOMMENDATION, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async editShopRecommendation(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.EDIT_SHOP_RECOMMENDATION, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.EDIT_SHOP_RECOMMENDATION, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async deleteShopRecommendation(id: string) {
|
||||
return this.delete(JOB_CARD_ROUTES.DELETE_SHOP_RECOMMENDATION, { params: { id } })
|
||||
}
|
||||
|
||||
async addAttachment(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.ADD_ATTACHMENT, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.ADD_ATTACHMENT, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async deleteAttachment(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.DELETE_ATTACHMENT, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.DELETE_ATTACHMENT, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async changeServiceWriter(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.CHANGE_SERVICE_WRITER, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.CHANGE_SERVICE_WRITER, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async changeSalesPerson(id: string, payload: ApiRequestBody<typeof JOB_CARD_ROUTES.CHANGE_SALES_PERSON, "post">) {
|
||||
return this.post(JOB_CARD_ROUTES.CHANGE_SALES_PERSON, payload, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const LABEL_ROUTES = {
|
||||
INDEX: "/api/labels",
|
||||
BY_ID: "/api/labels/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class LabelsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(LABEL_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof LABEL_ROUTES.INDEX, "post">) {
|
||||
return this.post(LABEL_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof LABEL_ROUTES.BY_ID, "put">) {
|
||||
return this.put(LABEL_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(LABEL_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const PARTS_ROUTES = {
|
||||
INDEX: "/api/parts",
|
||||
BY_ID: "/api/parts/{id}",
|
||||
IMPORT: "/api/import-parts",
|
||||
EXPORT: "/api/export-parts",
|
||||
TOGGLE_STATUS: "/api/toggle-part-status",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class PartsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(PARTS_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof PARTS_ROUTES.INDEX, "post">) {
|
||||
return this.post(PARTS_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof PARTS_ROUTES.BY_ID, "put">) {
|
||||
return this.put(PARTS_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(PARTS_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async import(payload: ApiRequestBody<typeof PARTS_ROUTES.IMPORT, "post">) {
|
||||
return this.post(PARTS_ROUTES.IMPORT, payload)
|
||||
}
|
||||
|
||||
async export(payload: ApiRequestBody<typeof PARTS_ROUTES.EXPORT, "post">) {
|
||||
return this.post(PARTS_ROUTES.EXPORT, payload)
|
||||
}
|
||||
|
||||
async toggleStatus(payload: ApiRequestBody<typeof PARTS_ROUTES.TOGGLE_STATUS, "post">) {
|
||||
return this.post(PARTS_ROUTES.TOGGLE_STATUS, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const PAYMENT_TERM_ROUTES = {
|
||||
INDEX: "/api/payment-terms",
|
||||
BY_ID: "/api/payment-terms/{id}",
|
||||
SET_DEFAULT: "/api/set-default-payment-term",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class PaymentTermsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(PAYMENT_TERM_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof PAYMENT_TERM_ROUTES.INDEX, "post">) {
|
||||
return this.post(PAYMENT_TERM_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof PAYMENT_TERM_ROUTES.BY_ID, "put">) {
|
||||
return this.put(PAYMENT_TERM_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(PAYMENT_TERM_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setDefault(payload: ApiRequestBody<typeof PAYMENT_TERM_ROUTES.SET_DEFAULT, "post">) {
|
||||
return this.post(PAYMENT_TERM_ROUTES.SET_DEFAULT, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const PAYMENT_ROUTES = {
|
||||
MODES: "/api/payment-mode",
|
||||
MODE_BY_ID: "/api/payment-mode/{id}",
|
||||
RECEIVED: "/api/payment-recieved",
|
||||
RECEIVED_BY_ID: "/api/payment-recieved/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class PaymentsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Payment Modes ──
|
||||
async listModes(query?: ApiListQueryParams) {
|
||||
return this.get(PAYMENT_ROUTES.MODES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createMode(payload: ApiRequestBody<typeof PAYMENT_ROUTES.MODES, "post">) {
|
||||
return this.post(PAYMENT_ROUTES.MODES, payload)
|
||||
}
|
||||
|
||||
async updateMode(id: string, payload: ApiRequestBody<typeof PAYMENT_ROUTES.MODE_BY_ID, "put">) {
|
||||
return this.put(PAYMENT_ROUTES.MODE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyMode(id: string) {
|
||||
return this.delete(PAYMENT_ROUTES.MODE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Payment Received ──
|
||||
async listReceived(query?: ApiListQueryParams) {
|
||||
return this.get(PAYMENT_ROUTES.RECEIVED, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createReceived(payload: ApiRequestBody<typeof PAYMENT_ROUTES.RECEIVED, "post">) {
|
||||
return this.post(PAYMENT_ROUTES.RECEIVED, payload)
|
||||
}
|
||||
|
||||
async updateReceived(id: string, payload: ApiRequestBody<typeof PAYMENT_ROUTES.RECEIVED_BY_ID, "post">) {
|
||||
return this.post(PAYMENT_ROUTES.RECEIVED_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyReceived(id: string) {
|
||||
return this.delete(PAYMENT_ROUTES.RECEIVED_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const PURCHASE_ORDER_ROUTES = {
|
||||
INDEX: "/api/purchase-orders",
|
||||
BY_ID: "/api/purchase-orders/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class PurchaseOrdersClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(PURCHASE_ORDER_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof PURCHASE_ORDER_ROUTES.INDEX, "post">) {
|
||||
return this.post(PURCHASE_ORDER_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof PURCHASE_ORDER_ROUTES.BY_ID, "put">) {
|
||||
return this.put(PURCHASE_ORDER_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(PURCHASE_ORDER_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const REFERRAL_SOURCE_ROUTES = {
|
||||
INDEX: "/api/referral-sources",
|
||||
BY_ID: "/api/referral-sources/{id}",
|
||||
SET_DEFAULT: "/api/set-default-referral-source",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ReferralSourcesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(REFERRAL_SOURCE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof REFERRAL_SOURCE_ROUTES.INDEX, "post">) {
|
||||
return this.post(REFERRAL_SOURCE_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof REFERRAL_SOURCE_ROUTES.BY_ID, "put">) {
|
||||
return this.put(REFERRAL_SOURCE_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(REFERRAL_SOURCE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setDefault(payload: ApiRequestBody<typeof REFERRAL_SOURCE_ROUTES.SET_DEFAULT, "post">) {
|
||||
return this.post(REFERRAL_SOURCE_ROUTES.SET_DEFAULT, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { CrudClient } from "../infra/crud-client"
|
||||
import type { ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath } from "../infra/types"
|
||||
|
||||
export const SERVICE_GROUP_ROUTES = {
|
||||
INDEX: "/api/service-groups",
|
||||
BY_ID: "/api/service-groups/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ServiceGroupsClient extends CrudClient<
|
||||
typeof SERVICE_GROUP_ROUTES.INDEX,
|
||||
typeof SERVICE_GROUP_ROUTES.BY_ID
|
||||
> {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions, SERVICE_GROUP_ROUTES.INDEX, SERVICE_GROUP_ROUTES.BY_ID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const SERVICE_ROUTES = {
|
||||
INDEX: "/api/services",
|
||||
BY_ID: "/api/services/{id}",
|
||||
IMPORT: "/api/import-services",
|
||||
EXPORT: "/api/export-services",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ServicesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(SERVICE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof SERVICE_ROUTES.INDEX, "post">) {
|
||||
return this.post(SERVICE_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof SERVICE_ROUTES.BY_ID, "put">) {
|
||||
return this.put(SERVICE_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(SERVICE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async import(payload: ApiRequestBody<typeof SERVICE_ROUTES.IMPORT, "post">) {
|
||||
return this.post(SERVICE_ROUTES.IMPORT, payload)
|
||||
}
|
||||
|
||||
async export(payload: ApiRequestBody<typeof SERVICE_ROUTES.EXPORT, "post">) {
|
||||
return this.post(SERVICE_ROUTES.EXPORT, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const SHOP_CALENDAR_ROUTES = {
|
||||
INDEX: "/api/shop-calenders",
|
||||
BY_ID: "/api/shop-calenders/{id}",
|
||||
SET_DEFAULT: "/api/set-default-shop-calender",
|
||||
REMOVE_DEFAULT: "/api/remove-default-shop-calender",
|
||||
UPDATE_DAY_TYPE: "/api/shop-calenders/{id}/update-day-type",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ShopCalendarsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(SHOP_CALENDAR_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof SHOP_CALENDAR_ROUTES.INDEX, "post">) {
|
||||
return this.post(SHOP_CALENDAR_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(SHOP_CALENDAR_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setDefault(payload: ApiRequestBody<typeof SHOP_CALENDAR_ROUTES.SET_DEFAULT, "post">) {
|
||||
return this.post(SHOP_CALENDAR_ROUTES.SET_DEFAULT, payload)
|
||||
}
|
||||
|
||||
async removeDefault(payload: ApiRequestBody<typeof SHOP_CALENDAR_ROUTES.REMOVE_DEFAULT, "post">) {
|
||||
return this.post(SHOP_CALENDAR_ROUTES.REMOVE_DEFAULT, payload)
|
||||
}
|
||||
|
||||
async updateDayType(id: string, payload: ApiRequestBody<typeof SHOP_CALENDAR_ROUTES.UPDATE_DAY_TYPE, "post">) {
|
||||
return this.post(SHOP_CALENDAR_ROUTES.UPDATE_DAY_TYPE, payload, { params: { id } } as never)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CrudClient } from "../infra/crud-client"
|
||||
import type { ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
|
||||
export const SHOP_TIMING_ROUTES = {
|
||||
INDEX: "/api/shop-timings",
|
||||
BY_ID: "/api/shop-timings/{id}",
|
||||
SET_DEFAULT: "/api/set-default-shop-timing",
|
||||
REMOVE_DEFAULT: "/api/remove-default-shop-timing",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class ShopTimingsClient extends CrudClient<
|
||||
typeof SHOP_TIMING_ROUTES.INDEX,
|
||||
typeof SHOP_TIMING_ROUTES.BY_ID
|
||||
> {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions, SHOP_TIMING_ROUTES.INDEX, SHOP_TIMING_ROUTES.BY_ID)
|
||||
}
|
||||
|
||||
async setDefault(payload: ApiRequestBody<typeof SHOP_TIMING_ROUTES.SET_DEFAULT, "post">) {
|
||||
return this.post(SHOP_TIMING_ROUTES.SET_DEFAULT, payload)
|
||||
}
|
||||
|
||||
async removeDefault(payload: ApiRequestBody<typeof SHOP_TIMING_ROUTES.REMOVE_DEFAULT, "post">) {
|
||||
return this.post(SHOP_TIMING_ROUTES.REMOVE_DEFAULT, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const SHOP_TYPE_ROUTES = {
|
||||
INDEX: "/api/shop-types",
|
||||
BY_ID: "/api/shop-types/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export type ShopTypeCreatePayload = {
|
||||
title: string
|
||||
shop_type?: string
|
||||
note?: string
|
||||
is_default?: boolean
|
||||
inspection?: File | null
|
||||
image?: File | null
|
||||
}
|
||||
|
||||
export type ShopTypeUpdatePayload = Partial<Omit<ShopTypeCreatePayload, "title">> & {
|
||||
title?: string
|
||||
}
|
||||
|
||||
function buildShopTypeFormData(payload: ShopTypeCreatePayload | ShopTypeUpdatePayload): FormData {
|
||||
const fd = new FormData()
|
||||
if (payload.title) fd.append("title", payload.title)
|
||||
if (payload.shop_type) fd.append("shop_type", payload.shop_type)
|
||||
if (payload.note) fd.append("note", payload.note)
|
||||
if (payload.is_default != null) fd.append("is_default", String(Number(payload.is_default)))
|
||||
if (payload.inspection instanceof File) fd.append("inspection", payload.inspection)
|
||||
if (payload.image instanceof File) fd.append("image", payload.image)
|
||||
return fd
|
||||
}
|
||||
|
||||
export class ShopTypesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(SHOP_TYPE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ShopTypeCreatePayload) {
|
||||
const fd = buildShopTypeFormData(payload)
|
||||
return this.postFormData(SHOP_TYPE_ROUTES.INDEX, fd)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ShopTypeUpdatePayload) {
|
||||
const fd = buildShopTypeFormData(payload)
|
||||
fd.append("_method", "PUT")
|
||||
const url = SHOP_TYPE_ROUTES.BY_ID.replace("{id}", id)
|
||||
return this.postFormData(url, fd)
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(SHOP_TYPE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const TASK_ROUTES = {
|
||||
TYPES: "/api/task-types",
|
||||
TYPE_BY_ID: "/api/task-types/{id}",
|
||||
SET_DEFAULT_TYPE: "/api/set-default-task-type",
|
||||
REMOVE_DEFAULT_TYPE: "/api/remove-default-task-type",
|
||||
SECTIONS: "/api/task-sections",
|
||||
SECTION_BY_ID: "/api/task-sections/{id}",
|
||||
SET_DEFAULT_SECTION: "/api/set-default-task-section",
|
||||
REMOVE_DEFAULT_SECTION: "/api/remove-default-task-section",
|
||||
CHANGE_SECTION_ARRANGEMENT: "/api/change-task-section-arrangement",
|
||||
TASKS: "/api/tasks",
|
||||
TASK_BY_ID: "/api/tasks/{id}",
|
||||
COMPLETE: "/api/tasks/{id}/complete",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class TasksClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Task Types ──
|
||||
async listTypes(query?: ApiListQueryParams) {
|
||||
return this.get(TASK_ROUTES.TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createType(payload: ApiRequestBody<typeof TASK_ROUTES.TYPES, "post">) {
|
||||
return this.post(TASK_ROUTES.TYPES, payload)
|
||||
}
|
||||
|
||||
async updateType(id: string, payload: ApiRequestBody<typeof TASK_ROUTES.TYPE_BY_ID, "put">) {
|
||||
return this.put(TASK_ROUTES.TYPE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyType(id: string) {
|
||||
return this.delete(TASK_ROUTES.TYPE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setDefaultType(payload: ApiRequestBody<typeof TASK_ROUTES.SET_DEFAULT_TYPE, "post">) {
|
||||
return this.post(TASK_ROUTES.SET_DEFAULT_TYPE, payload)
|
||||
}
|
||||
|
||||
async removeDefaultType(payload: ApiRequestBody<typeof TASK_ROUTES.REMOVE_DEFAULT_TYPE, "post">) {
|
||||
return this.post(TASK_ROUTES.REMOVE_DEFAULT_TYPE, payload)
|
||||
}
|
||||
|
||||
// ── Task Sections ──
|
||||
async listSections(query?: ApiListQueryParams) {
|
||||
return this.get(TASK_ROUTES.SECTIONS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createSection(payload: ApiRequestBody<typeof TASK_ROUTES.SECTIONS, "post">) {
|
||||
return this.post(TASK_ROUTES.SECTIONS, payload)
|
||||
}
|
||||
|
||||
async updateSection(id: string, payload: ApiRequestBody<typeof TASK_ROUTES.SECTION_BY_ID, "put">) {
|
||||
return this.put(TASK_ROUTES.SECTION_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroySection(id: string) {
|
||||
return this.delete(TASK_ROUTES.SECTION_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async setDefaultSection(payload: ApiRequestBody<typeof TASK_ROUTES.SET_DEFAULT_SECTION, "post">) {
|
||||
return this.post(TASK_ROUTES.SET_DEFAULT_SECTION, payload)
|
||||
}
|
||||
|
||||
async removeDefaultSection(payload: ApiRequestBody<typeof TASK_ROUTES.REMOVE_DEFAULT_SECTION, "post">) {
|
||||
return this.post(TASK_ROUTES.REMOVE_DEFAULT_SECTION, payload)
|
||||
}
|
||||
|
||||
async changeSectionArrangement(payload: ApiRequestBody<typeof TASK_ROUTES.CHANGE_SECTION_ARRANGEMENT, "post">) {
|
||||
return this.post(TASK_ROUTES.CHANGE_SECTION_ARRANGEMENT, payload)
|
||||
}
|
||||
|
||||
// ── Tasks ──
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(TASK_ROUTES.TASKS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof TASK_ROUTES.TASKS, "post">) {
|
||||
return this.post(TASK_ROUTES.TASKS, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof TASK_ROUTES.TASK_BY_ID, "put">) {
|
||||
return this.put(TASK_ROUTES.TASK_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(TASK_ROUTES.TASK_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async complete(id: string, payload: ApiRequestBody<typeof TASK_ROUTES.COMPLETE, "post">) {
|
||||
return this.post(TASK_ROUTES.COMPLETE, payload, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const VEHICLE_ATTRIBUTE_ROUTES = {
|
||||
BODY_TYPES: "/api/vehicle-body-types",
|
||||
BODY_TYPE_BY_ID: "/api/vehicle-body-types/{id}",
|
||||
FUEL_TYPES: "/api/vehicle-fuel-types",
|
||||
FUEL_TYPE_BY_ID: "/api/vehicle-fuel-types/{id}",
|
||||
TRANSMISSIONS: "/api/vehicle-transmissions",
|
||||
TRANSMISSION_BY_ID: "/api/vehicle-transmissions/{id}",
|
||||
COLORS: "/api/vehicle-colors",
|
||||
COLOR_BY_ID: "/api/vehicle-colors/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class VehicleAttributesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Body Types ──
|
||||
async listBodyTypes(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createBodyType(payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPES, "post">) {
|
||||
return this.post(VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPES, payload)
|
||||
}
|
||||
|
||||
async updateBodyType(id: string, payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPE_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyBodyType(id: string) {
|
||||
return this.delete(VEHICLE_ATTRIBUTE_ROUTES.BODY_TYPE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Fuel Types ──
|
||||
async listFuelTypes(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createFuelType(payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPES, "post">) {
|
||||
return this.post(VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPES, payload)
|
||||
}
|
||||
|
||||
async updateFuelType(id: string, payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPE_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyFuelType(id: string) {
|
||||
return this.delete(VEHICLE_ATTRIBUTE_ROUTES.FUEL_TYPE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Transmissions ──
|
||||
async listTransmissions(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSIONS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createTransmission(payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSIONS, "post">) {
|
||||
return this.post(VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSIONS, payload)
|
||||
}
|
||||
|
||||
async updateTransmission(id: string, payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSION_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSION_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyTransmission(id: string) {
|
||||
return this.delete(VEHICLE_ATTRIBUTE_ROUTES.TRANSMISSION_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Colors ──
|
||||
async listColors(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_ATTRIBUTE_ROUTES.COLORS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createColor(payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.COLORS, "post">) {
|
||||
return this.post(VEHICLE_ATTRIBUTE_ROUTES.COLORS, payload)
|
||||
}
|
||||
|
||||
async updateColor(id: string, payload: ApiRequestBody<typeof VEHICLE_ATTRIBUTE_ROUTES.COLOR_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_ATTRIBUTE_ROUTES.COLOR_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyColor(id: string) {
|
||||
return this.delete(VEHICLE_ATTRIBUTE_ROUTES.COLOR_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const VEHICLE_DOCUMENT_ROUTES = {
|
||||
DOCUMENT_TYPES: "/api/document-types",
|
||||
DOCUMENT_TYPE_BY_ID: "/api/document-types/{id}",
|
||||
DOCUMENTS: "/api/vehicle-documents",
|
||||
DOCUMENT_BY_ID: "/api/vehicle-documents/{id}",
|
||||
MILEAGE: "/api/vehicle-mile-and-kms",
|
||||
MILEAGE_BY_ID: "/api/vehicle-mile-and-kms/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class VehicleDocumentsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
// ── Document Types ──
|
||||
async listDocumentTypes(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPES, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createDocumentType(payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPES, "post">) {
|
||||
return this.post(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPES, payload)
|
||||
}
|
||||
|
||||
async updateDocumentType(id: string, payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPE_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyDocumentType(id: string) {
|
||||
return this.delete(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_TYPE_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Vehicle Documents ──
|
||||
async listDocuments(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_DOCUMENT_ROUTES.DOCUMENTS, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createDocument(payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.DOCUMENTS, "post">) {
|
||||
return this.post(VEHICLE_DOCUMENT_ROUTES.DOCUMENTS, payload)
|
||||
}
|
||||
|
||||
async updateDocument(id: string, payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.DOCUMENT_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyDocument(id: string) {
|
||||
return this.delete(VEHICLE_DOCUMENT_ROUTES.DOCUMENT_BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
// ── Mileage ──
|
||||
async listMileage(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_DOCUMENT_ROUTES.MILEAGE, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async createMileage(payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.MILEAGE, "post">) {
|
||||
return this.post(VEHICLE_DOCUMENT_ROUTES.MILEAGE, payload)
|
||||
}
|
||||
|
||||
async updateMileage(id: string, payload: ApiRequestBody<typeof VEHICLE_DOCUMENT_ROUTES.MILEAGE_BY_ID, "put">) {
|
||||
return this.put(VEHICLE_DOCUMENT_ROUTES.MILEAGE_BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroyMileage(id: string) {
|
||||
return this.delete(VEHICLE_DOCUMENT_ROUTES.MILEAGE_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const VEHICLE_ROUTES = {
|
||||
INDEX: "/api/vehicles",
|
||||
BY_ID: "/api/vehicles/{id}",
|
||||
EXPORT: "/api/vehicles/export",
|
||||
IMPORT: "/api/vehicles/import",
|
||||
GET_OWNERS: "/api/get-vehicle-owners",
|
||||
LINK_CUSTOMER: "/api/link-customer-to-vehicle",
|
||||
UNLINK_CUSTOMER: "/api/unlink-customer-from-vehicle",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class VehiclesClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(VEHICLE_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof VEHICLE_ROUTES.INDEX, "post">) {
|
||||
return this.post(VEHICLE_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof VEHICLE_ROUTES.BY_ID, "put">) {
|
||||
return this.put(VEHICLE_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(VEHICLE_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async export() {
|
||||
return this.get(VEHICLE_ROUTES.EXPORT)
|
||||
}
|
||||
|
||||
async import(payload: ApiRequestBody<typeof VEHICLE_ROUTES.IMPORT, "post">) {
|
||||
return this.post(VEHICLE_ROUTES.IMPORT, payload)
|
||||
}
|
||||
|
||||
async getOwners() {
|
||||
return this.get(VEHICLE_ROUTES.GET_OWNERS)
|
||||
}
|
||||
|
||||
async linkCustomer(payload: ApiRequestBody<typeof VEHICLE_ROUTES.LINK_CUSTOMER, "post">) {
|
||||
return this.post(VEHICLE_ROUTES.LINK_CUSTOMER, payload)
|
||||
}
|
||||
|
||||
async unlinkCustomer(payload: ApiRequestBody<typeof VEHICLE_ROUTES.UNLINK_CUSTOMER, "post">) {
|
||||
return this.post(VEHICLE_ROUTES.UNLINK_CUSTOMER, payload)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ApiClient, type ApiClientOptions } from "../infra/client"
|
||||
import type { ApiPath, ApiRequestBody } from "../infra/types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const VENDOR_ROUTES = {
|
||||
INDEX: "/api/vendors",
|
||||
BY_ID: "/api/vendors/{id}",
|
||||
TOGGLE_STATUS: "/api/toggle-vendor-status",
|
||||
CREATE_ADDRESS: "/api/create-vendor-address",
|
||||
ADDRESS_BY_ID: "/api/vendor-address/{id}",
|
||||
} as const satisfies Record<string, ApiPath>
|
||||
|
||||
export class VendorsClient extends ApiClient {
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) {
|
||||
super(baseUrl, defaultOptions)
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams) {
|
||||
return this.get(VENDOR_ROUTES.INDEX, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<typeof VENDOR_ROUTES.INDEX, "post">) {
|
||||
return this.post(VENDOR_ROUTES.INDEX, payload)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<typeof VENDOR_ROUTES.BY_ID, "put">) {
|
||||
return this.put(VENDOR_ROUTES.BY_ID, payload, { params: { id } })
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete(VENDOR_ROUTES.BY_ID, { params: { id } })
|
||||
}
|
||||
|
||||
async toggleStatus(payload: ApiRequestBody<typeof VENDOR_ROUTES.TOGGLE_STATUS, "post">) {
|
||||
return this.post(VENDOR_ROUTES.TOGGLE_STATUS, payload)
|
||||
}
|
||||
|
||||
async createAddress(payload: ApiRequestBody<typeof VENDOR_ROUTES.CREATE_ADDRESS, "post">) {
|
||||
return this.post(VENDOR_ROUTES.CREATE_ADDRESS, payload)
|
||||
}
|
||||
|
||||
async getAddress(id: string) {
|
||||
return this.get(VENDOR_ROUTES.ADDRESS_BY_ID, { params: { id } })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiBaseResponse, ApiListQueryParams } from "./types"
|
||||
|
||||
export interface CrudOperations {
|
||||
list: <T>(params: ApiListQueryParams) => Promise<ApiBaseResponse<T>>
|
||||
create: <T>(payload: unknown) => Promise<ApiBaseResponse<T>>
|
||||
update: <T>(id: string, payload: unknown) => Promise<ApiBaseResponse<T>>
|
||||
destroy: <T>(id: string) => Promise<ApiBaseResponse<T>>
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export type ApiBaseResponse<T> = {
|
||||
data: T;
|
||||
meta: {
|
||||
current_page: number,
|
||||
last_page: number,
|
||||
per_page: number,
|
||||
total: number,
|
||||
from: number,
|
||||
to: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ApiListQueryParams = {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
sort_by?: string;
|
||||
sort_order?: 'asc' | 'desc';
|
||||
[key: string]: any; // For additional filters
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// ── Infrastructure ──
|
||||
export * from "./infra/index"
|
||||
|
||||
// ── Contracts ──
|
||||
export * from "./contracts/types"
|
||||
|
||||
// ── Domain Clients ──
|
||||
export * from "./clients/index"
|
||||
|
||||
// ── Factory ──
|
||||
export { createApi, api } from "./api"
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import type {
|
||||
ApiPath,
|
||||
ApiPathByMethod,
|
||||
ApiPathParams,
|
||||
ApiQueryParams,
|
||||
ApiRequestBody,
|
||||
ApiResponse,
|
||||
} from "./types"
|
||||
import createClient from "openapi-fetch"
|
||||
import type { paths } from "../../types/index"
|
||||
|
||||
type HttpMethod = "get" | "post" | "put" | "delete" | "patch"
|
||||
|
||||
export type ApiClientOptions = {
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
type ApiRequestOptions<Path extends ApiPath, Method extends HttpMethod> =
|
||||
Omit<RequestInit, "method" | "body"> & {
|
||||
params?: ApiPathParams<Path, Method> extends never ? never : ApiPathParams<Path, Method>
|
||||
query?: ApiQueryParams<Path, Method> extends never ? never : ApiQueryParams<Path, Method>
|
||||
body?: ApiRequestBody<Path, Method> extends never ? never : ApiRequestBody<Path, Method>
|
||||
}
|
||||
|
||||
type LaravelValidationErrors = Record<string, string[]>
|
||||
|
||||
type LaravelErrorPayload = {
|
||||
success?: boolean
|
||||
message?: string
|
||||
data?: unknown
|
||||
errors?: LaravelValidationErrors | string[] | Record<string, unknown> | null
|
||||
pagination?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly name = "ApiError"
|
||||
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
public readonly statusText: string,
|
||||
public readonly endpoint: string,
|
||||
public readonly method: string,
|
||||
public readonly payload?: LaravelErrorPayload,
|
||||
) {
|
||||
super(payload?.message ?? `${method.toUpperCase()} ${endpoint} failed with ${status} ${statusText}`.trim())
|
||||
}
|
||||
|
||||
get validationErrors(): LaravelValidationErrors | undefined {
|
||||
return this.payload?.errors && !Array.isArray(this.payload.errors)
|
||||
? (this.payload.errors as LaravelValidationErrors)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiClient {
|
||||
private client
|
||||
|
||||
constructor(
|
||||
protected baseUrl: string = process.env.NEXT_PUBLIC_API_URL ?? "",
|
||||
protected defaultOptions: ApiClientOptions = {},
|
||||
) {
|
||||
this.client = createClient<paths>({
|
||||
baseUrl: `${this.normalizeBaseUrl(baseUrl)}/`,
|
||||
})
|
||||
}
|
||||
|
||||
async request<Path extends ApiPath, Method extends HttpMethod>(
|
||||
endpoint: Path,
|
||||
method: Method,
|
||||
options: ApiRequestOptions<Path, Method> = {} as ApiRequestOptions<Path, Method>,
|
||||
): Promise<ApiResponse<Path, Method>> {
|
||||
const ep = endpoint as never
|
||||
const opts = options as never
|
||||
const body = (options as Record<string, unknown>).body as never
|
||||
|
||||
switch (method) {
|
||||
case "get":
|
||||
return this.get(ep, opts) as Promise<ApiResponse<Path, Method>>
|
||||
case "post":
|
||||
return this.post(ep, body, opts) as Promise<ApiResponse<Path, Method>>
|
||||
case "put":
|
||||
return this.put(ep, body, opts) as Promise<ApiResponse<Path, Method>>
|
||||
case "delete":
|
||||
return this.delete(ep, opts) as Promise<ApiResponse<Path, Method>>
|
||||
case "patch":
|
||||
return this.patch(ep, body, opts) as Promise<ApiResponse<Path, Method>>
|
||||
default:
|
||||
throw new ApiError(0, "Unsupported Method", endpoint, method, {
|
||||
message: `Unsupported method: ${String(method)}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async get<Path extends ApiPathByMethod<"get">>(
|
||||
endpoint: Path,
|
||||
options: ApiRequestOptions<Path, "get"> = {} as ApiRequestOptions<Path, "get">,
|
||||
): Promise<ApiResponse<Path, "get">> {
|
||||
const requestOptions = this.toFetchOptions(options)
|
||||
|
||||
try {
|
||||
const { data, error, response } = await this.client.GET(endpoint, requestOptions as never)
|
||||
return this.resolveResult(endpoint, "get", data, error, response)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) throw err
|
||||
throw this.createNetworkError(endpoint, "get")
|
||||
}
|
||||
}
|
||||
|
||||
async post<Path extends ApiPathByMethod<"post">>(
|
||||
endpoint: Path,
|
||||
body: ApiRequestBody<Path, "post"> extends never ? undefined : ApiRequestBody<Path, "post">,
|
||||
options: Omit<ApiRequestOptions<Path, "post">, "body"> = {} as Omit<ApiRequestOptions<Path, "post">, "body">,
|
||||
): Promise<ApiResponse<Path, "post">> {
|
||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||
|
||||
try {
|
||||
const { data, error, response } = await this.client.POST(endpoint, requestOptions as never)
|
||||
return this.resolveResult(endpoint, "post", data, error, response)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) throw err
|
||||
throw this.createNetworkError(endpoint, "post")
|
||||
}
|
||||
}
|
||||
|
||||
async put<Path extends ApiPathByMethod<"put">>(
|
||||
endpoint: Path,
|
||||
body: ApiRequestBody<Path, "put"> extends never ? undefined : ApiRequestBody<Path, "put">,
|
||||
options: Omit<ApiRequestOptions<Path, "put">, "body"> = {} as Omit<ApiRequestOptions<Path, "put">, "body">,
|
||||
): Promise<ApiResponse<Path, "put">> {
|
||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||
|
||||
try {
|
||||
const { data, error, response } = await this.client.PUT(endpoint, requestOptions as never)
|
||||
return this.resolveResult(endpoint, "put", data, error, response)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) throw err
|
||||
throw this.createNetworkError(endpoint, "put")
|
||||
}
|
||||
}
|
||||
|
||||
async delete<Path extends ApiPathByMethod<"delete">>(
|
||||
endpoint: Path,
|
||||
options: ApiRequestOptions<Path, "delete"> = {} as ApiRequestOptions<Path, "delete">,
|
||||
): Promise<ApiResponse<Path, "delete">> {
|
||||
const requestOptions = this.toFetchOptions(options)
|
||||
|
||||
try {
|
||||
const { data, error, response } = await this.client.DELETE(endpoint, requestOptions as never)
|
||||
return this.resolveResult(endpoint, "delete", data, error, response)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) throw err
|
||||
throw this.createNetworkError(endpoint, "delete")
|
||||
}
|
||||
}
|
||||
|
||||
async patch<Path extends ApiPathByMethod<"patch">>(
|
||||
endpoint: Path,
|
||||
body: ApiRequestBody<Path, "patch"> extends never ? undefined : ApiRequestBody<Path, "patch">,
|
||||
options: Omit<ApiRequestOptions<Path, "patch">, "body"> = {} as Omit<ApiRequestOptions<Path, "patch">, "body">,
|
||||
): Promise<ApiResponse<Path, "patch">> {
|
||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||
|
||||
try {
|
||||
const { data, error, response } = await this.client.PATCH(endpoint, requestOptions as never)
|
||||
return this.resolveResult(endpoint, "patch", data, error, response)
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) throw err
|
||||
throw this.createNetworkError(endpoint, "patch")
|
||||
}
|
||||
}
|
||||
|
||||
protected normalizeBaseUrl(baseUrl: string): string {
|
||||
return baseUrl.replace(/\/+$/, "")
|
||||
}
|
||||
|
||||
protected async postFormData(endpoint: string, formData: FormData): Promise<any> {
|
||||
const url = `${this.normalizeBaseUrl(this.baseUrl)}${endpoint}`
|
||||
const headers = new Headers(this.defaultOptions.headers as Record<string, string>)
|
||||
headers.set("Accept", "application/json")
|
||||
// Content-Type is intentionally omitted — fetch sets multipart/form-data + boundary automatically
|
||||
|
||||
const response = await fetch(url, { method: "POST", headers, body: formData })
|
||||
const text = await response.text()
|
||||
const data = text ? JSON.parse(text) : null
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(response.status, response.statusText, endpoint, "post", data)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
private toFetchOptions<Path extends ApiPath, Method extends HttpMethod>(
|
||||
options: ApiRequestOptions<Path, Method>,
|
||||
): Record<string, unknown> {
|
||||
const { params, query, body, headers, ...requestInit } = options as Record<string, unknown>
|
||||
const requestOptions: Record<string, unknown> = {
|
||||
...requestInit,
|
||||
headers: this.withDefaultHeaders(headers as HeadersInit | undefined),
|
||||
}
|
||||
|
||||
if (params || query) {
|
||||
requestOptions.params = {
|
||||
...(params ? { path: params } : {}),
|
||||
...(query ? { query } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (body !== undefined) {
|
||||
requestOptions.body = body
|
||||
}
|
||||
|
||||
return requestOptions
|
||||
}
|
||||
|
||||
private withDefaultHeaders(headers?: HeadersInit): Headers {
|
||||
const finalHeaders = new Headers(this.defaultOptions.headers)
|
||||
finalHeaders.set("Accept", "application/json")
|
||||
if (headers) {
|
||||
new Headers(headers).forEach((value, key) => finalHeaders.set(key, value))
|
||||
}
|
||||
return finalHeaders
|
||||
}
|
||||
|
||||
private resolveResult<Path extends ApiPath, Method extends HttpMethod>(
|
||||
endpoint: Path,
|
||||
method: Method,
|
||||
data: unknown,
|
||||
error: unknown,
|
||||
response: Response,
|
||||
): ApiResponse<Path, Method> {
|
||||
if (error !== undefined) {
|
||||
throw new ApiError(
|
||||
response.status,
|
||||
response.statusText,
|
||||
endpoint,
|
||||
method,
|
||||
this.normalizeErrorPayload(error),
|
||||
)
|
||||
}
|
||||
|
||||
return data as ApiResponse<Path, Method>
|
||||
}
|
||||
|
||||
private normalizeErrorPayload(error: unknown): LaravelErrorPayload | undefined {
|
||||
if (!error || typeof error !== "object") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if ("message" in error || "errors" in error) {
|
||||
return error as LaravelErrorPayload
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Request failed",
|
||||
data: error,
|
||||
}
|
||||
}
|
||||
|
||||
private createNetworkError(endpoint: string, method: string): ApiError {
|
||||
return new ApiError(
|
||||
0,
|
||||
"Network Error",
|
||||
endpoint,
|
||||
method,
|
||||
{ message: "Network error occurred. Please check your connection and try again." },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ApiClient, type ApiClientOptions } from "./client"
|
||||
import type { ApiPathByMethod, ApiQueryParams, ApiRequestBody, ApiResponse } from "./types"
|
||||
import type { ApiListQueryParams } from "../contracts/types"
|
||||
|
||||
export const DEFAULT_PER_PAGE = 10
|
||||
|
||||
type CrudIndexRoute = ApiPathByMethod<"get"> & ApiPathByMethod<"post">
|
||||
type CrudByIdRoute = ApiPathByMethod<"put"> & ApiPathByMethod<"delete">
|
||||
|
||||
export abstract class CrudClient<
|
||||
IndexRoute extends CrudIndexRoute,
|
||||
ByIdRoute extends CrudByIdRoute,
|
||||
> extends ApiClient {
|
||||
|
||||
|
||||
constructor(baseUrl?: string, defaultOptions?: ApiClientOptions,
|
||||
|
||||
public indexRoute?: IndexRoute,
|
||||
public byIdRoute?: ByIdRoute) {
|
||||
|
||||
super(baseUrl, defaultOptions)
|
||||
|
||||
}
|
||||
|
||||
async list(query?: ApiListQueryParams): Promise<ApiResponse<IndexRoute, "get">> {
|
||||
return this.get(this.indexRoute as IndexRoute, query ? { query } as never : undefined)
|
||||
}
|
||||
|
||||
async show(id: string) {
|
||||
return this.get(this.byIdRoute as ByIdRoute & ApiPathByMethod<"get">, { params: { id } } as never)
|
||||
}
|
||||
|
||||
async create(payload: ApiRequestBody<IndexRoute, "post">) {
|
||||
return this.post<IndexRoute>(this.indexRoute as IndexRoute, payload as never)
|
||||
}
|
||||
|
||||
async update(id: string, payload: ApiRequestBody<ByIdRoute, "put">) {
|
||||
return this.put<ByIdRoute>(this.byIdRoute as ByIdRoute, payload as never, { params: { id } } as never)
|
||||
}
|
||||
|
||||
async destroy(id: string) {
|
||||
return this.delete<ByIdRoute>(this.byIdRoute as ByIdRoute, { params: { id } } as never)
|
||||
}
|
||||
}
|
||||
|
||||
export type BaseCrudItem = { id: number }
|
||||
|
||||
/** Extract the list (GET index) response type from a CrudClient subclass. */
|
||||
export type CrudListResponse<C> = C extends CrudClient<infer IR, infer _BR> ? ApiResponse<IR, "get"> : never
|
||||
|
||||
/** Extract the show (GET by-id) response type from a CrudClient subclass. */
|
||||
export type CrudShowResponse<C> = C extends CrudClient<infer _IR, infer BR> ? ApiResponse<BR, "get"> : never
|
||||
|
||||
/** Extract a single item type from the `data` array of a CrudClient list response. */
|
||||
export type CrudListItem<C> = CrudListResponse<C> extends { data?: (infer Item)[] } ? Item : never
|
||||
|
||||
/** Extract the query-parameter type accepted by a CrudClient's `list()` method. */
|
||||
export type CrudListParams<C> = C extends CrudClient<infer IR, infer _BR> ? ApiQueryParams<IR, "get"> : never
|
||||
@@ -0,0 +1,22 @@
|
||||
export {
|
||||
type ApiPaths,
|
||||
type ApiComponents,
|
||||
type ApiOperations,
|
||||
type ApiPath,
|
||||
type ApiMethod,
|
||||
type ApiPathByMethod,
|
||||
type ApiPathParams,
|
||||
type ApiQueryParams,
|
||||
type ApiHeaderParams,
|
||||
type ApiCookieParams,
|
||||
type ApiRequestBody,
|
||||
type ApiResponse,
|
||||
type ApiOperationId,
|
||||
type ApiOperationRequestBody,
|
||||
type ApiOperationResponse,
|
||||
} from "./types"
|
||||
|
||||
export { ApiClient, ApiError, type ApiClientOptions } from "./client"
|
||||
export { DEFAULT_PER_PAGE } from "./crud-client"
|
||||
export * from "./crud-client"
|
||||
export type { AuthUser } from "./token"
|
||||
@@ -0,0 +1,6 @@
|
||||
export type AuthUser = {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
[key: string]: string | number
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { components, operations, paths } from "../../types/index"
|
||||
|
||||
type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"
|
||||
type NotNeverOrUndefined<T> = [Exclude<T, undefined>] extends [never] ? never : Exclude<T, undefined>
|
||||
|
||||
type OperationFor<Path extends ApiPath, Method extends HttpMethod> = Method extends keyof paths[Path]
|
||||
? NotNeverOrUndefined<paths[Path][Method]>
|
||||
: never
|
||||
|
||||
type WithContent<T> = T extends { content: infer Content } ? Content : never
|
||||
type JsonContent<T> = T extends { "application/json": infer Payload } ? Payload : never
|
||||
type RequestContent<T> =
|
||||
T extends { "application/json": infer Payload } ? Payload :
|
||||
T extends { "*/*": infer Payload } ? Payload :
|
||||
T extends { "multipart/form-data": infer Payload } ? Payload :
|
||||
never
|
||||
|
||||
type ResponseByCode<Responses, Code extends number> = Code extends keyof Responses
|
||||
? Responses[Code]
|
||||
: never
|
||||
|
||||
type SuccessResponse<Responses> =
|
||||
| ResponseByCode<Responses, 200>
|
||||
| ResponseByCode<Responses, 201>
|
||||
| ResponseByCode<Responses, 202>
|
||||
| ResponseByCode<Responses, 203>
|
||||
| ResponseByCode<Responses, 204>
|
||||
| ResponseByCode<Responses, 205>
|
||||
| ResponseByCode<Responses, 206>
|
||||
| ResponseByCode<Responses, 207>
|
||||
| ResponseByCode<Responses, 208>
|
||||
| ResponseByCode<Responses, 226>
|
||||
| ("default" extends keyof Responses ? Responses["default"] : never)
|
||||
|
||||
// ── Re-exports ──
|
||||
export type ApiPaths = paths
|
||||
export type ApiComponents = components
|
||||
export type ApiOperations = operations
|
||||
|
||||
// ── Path & Method helpers ──
|
||||
export type ApiPath = keyof paths
|
||||
export type ApiMethod<Path extends ApiPath = ApiPath> = {
|
||||
[Method in HttpMethod]: OperationFor<Path, Method> extends never ? never : Method
|
||||
}[HttpMethod]
|
||||
export type ApiPathByMethod<Method extends HttpMethod> = {
|
||||
[Path in ApiPath]: OperationFor<Path, Method> extends never ? never : Path
|
||||
}[ApiPath]
|
||||
|
||||
// ── Parameter helpers ──
|
||||
export type ApiPathParams<Path extends ApiPath, Method extends HttpMethod> =
|
||||
OperationFor<Path, Method> extends { parameters: { path: infer Params } }
|
||||
? Params
|
||||
: never
|
||||
|
||||
export type ApiQueryParams<Path extends ApiPath, Method extends HttpMethod> =
|
||||
OperationFor<Path, Method> extends { parameters: { query: infer Params } }
|
||||
? Params
|
||||
: never
|
||||
|
||||
export type ApiHeaderParams<Path extends ApiPath, Method extends HttpMethod> =
|
||||
OperationFor<Path, Method> extends { parameters: { header: infer Params } }
|
||||
? Params
|
||||
: never
|
||||
|
||||
export type ApiCookieParams<Path extends ApiPath, Method extends HttpMethod> =
|
||||
OperationFor<Path, Method> extends { parameters: { cookie: infer Params } }
|
||||
? Params
|
||||
: never
|
||||
|
||||
// ── Request / Response body helpers ──
|
||||
export type ApiRequestBody<Path extends ApiPath, Method extends HttpMethod> =
|
||||
RequestContent<
|
||||
WithContent<
|
||||
OperationFor<Path, Method> extends { requestBody?: infer RequestBody }
|
||||
? RequestBody
|
||||
: never
|
||||
>
|
||||
>
|
||||
|
||||
export type ApiResponse<Path extends ApiPath, Method extends HttpMethod> =
|
||||
JsonContent<
|
||||
WithContent<
|
||||
SuccessResponse<
|
||||
OperationFor<Path, Method> extends { responses: infer Responses }
|
||||
? Responses
|
||||
: never
|
||||
>
|
||||
>
|
||||
>
|
||||
|
||||
// ── Operation-level helpers ──
|
||||
export type ApiOperationId = keyof operations
|
||||
|
||||
export type ApiOperationRequestBody<OperationId extends ApiOperationId> =
|
||||
RequestContent<
|
||||
WithContent<
|
||||
operations[OperationId] extends { requestBody?: infer RequestBody }
|
||||
? RequestBody
|
||||
: never
|
||||
>
|
||||
>
|
||||
|
||||
export type ApiOperationResponse<OperationId extends ApiOperationId> =
|
||||
JsonContent<
|
||||
WithContent<
|
||||
SuccessResponse<
|
||||
operations[OperationId] extends { responses: infer Responses }
|
||||
? Responses
|
||||
: never
|
||||
>
|
||||
>
|
||||
>
|
||||
@@ -0,0 +1,24 @@
|
||||
import "server-only"
|
||||
import { cookies } from "next/headers"
|
||||
import { createApi } from "./api"
|
||||
import type { AuthUser } from "./infra/token"
|
||||
|
||||
export async function getServerApi() {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get("auth_token")?.value
|
||||
|
||||
return createApi(
|
||||
token ? { headers: { Authorization: `Bearer ${token}` } } : undefined,
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerUser(): Promise<AuthUser | null> {
|
||||
const cookieStore = await cookies()
|
||||
const raw = cookieStore.get("auth_user")?.value
|
||||
if (!raw) return null
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(raw)) as AuthUser
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler"
|
||||
},
|
||||
"include": ["src/**/*.ts", "types/**/*.ts"]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
# `@turbo/eslint-config`
|
||||
|
||||
Collection of internal eslint configurations.
|
||||
@@ -0,0 +1,32 @@
|
||||
import js from "@eslint/js";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import turboPlugin from "eslint-plugin-turbo";
|
||||
import tseslint from "typescript-eslint";
|
||||
import onlyWarn from "eslint-plugin-only-warn";
|
||||
|
||||
/**
|
||||
* A shared ESLint configuration for the repository.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config[]}
|
||||
* */
|
||||
export const config = [
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
plugins: {
|
||||
turbo: turboPlugin,
|
||||
},
|
||||
rules: {
|
||||
"turbo/no-undeclared-env-vars": "warn",
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
onlyWarn,
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ["dist/**"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,57 @@
|
||||
import js from "@eslint/js";
|
||||
import { globalIgnores } from "eslint/config";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import tseslint from "typescript-eslint";
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks";
|
||||
import pluginReact from "eslint-plugin-react";
|
||||
import globals from "globals";
|
||||
import pluginNext from "@next/eslint-plugin-next";
|
||||
import { config as baseConfig } from "./base.js";
|
||||
|
||||
/**
|
||||
* A custom ESLint configuration for libraries that use Next.js.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config[]}
|
||||
* */
|
||||
export const nextJsConfig = [
|
||||
...baseConfig,
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
{
|
||||
...pluginReact.configs.flat.recommended,
|
||||
languageOptions: {
|
||||
...pluginReact.configs.flat.recommended.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"@next/next": pluginNext,
|
||||
},
|
||||
rules: {
|
||||
...pluginNext.configs.recommended.rules,
|
||||
...pluginNext.configs["core-web-vitals"].rules,
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"react-hooks": pluginReactHooks,
|
||||
},
|
||||
settings: { react: { version: "detect" } },
|
||||
rules: {
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
// React scope no longer necessary with new JSX transform.
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@repo/eslint-config",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./base": "./base.js",
|
||||
"./next-js": "./next.js",
|
||||
"./react-internal": "./react-internal.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@next/eslint-plugin-next": "^16.2.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-only-warn": "^1.1.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-turbo": "^2.7.1",
|
||||
"globals": "^16.5.0",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.50.0"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import js from "@eslint/js";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import tseslint from "typescript-eslint";
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks";
|
||||
import pluginReact from "eslint-plugin-react";
|
||||
import globals from "globals";
|
||||
import { config as baseConfig } from "./base.js";
|
||||
|
||||
/**
|
||||
* A custom ESLint configuration for libraries that use React.
|
||||
*
|
||||
* @type {import("eslint").Linter.Config[]} */
|
||||
export const config = [
|
||||
...baseConfig,
|
||||
js.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
...tseslint.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
...pluginReact.configs.flat.recommended.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"react-hooks": pluginReactHooks,
|
||||
},
|
||||
settings: { react: { version: "detect" } },
|
||||
rules: {
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
// React scope no longer necessary with new JSX transform.
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||
"module": "NodeNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [{ "name": "next" }],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowJs": true,
|
||||
"jsx": "preserve",
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@repo/typescript-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { config } from "@repo/eslint-config/react-internal";
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default config;
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@repo/ui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./*": "./src/*.tsx"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"generate:component": "turbo gen react-component",
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
appName: string;
|
||||
}
|
||||
|
||||
export const Button = ({ children, className, appName }: ButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
onClick={() => alert(`Hello from your ${appName} app!`)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { type JSX } from "react";
|
||||
|
||||
export function Card({
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
href,
|
||||
}: {
|
||||
className?: string;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
href: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<a
|
||||
className={className}
|
||||
href={`${href}?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<h2>
|
||||
{title} <span>-></span>
|
||||
</h2>
|
||||
<p>{children}</p>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type JSX } from "react";
|
||||
|
||||
export function Code({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
return <code className={className}>{children}</code>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@repo/typescript-config/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user