1179 lines
22 KiB
Markdown
1179 lines
22 KiB
Markdown
# API Reference for Front-end Developers
|
|
|
|
**Last Updated:** 2026-04-25
|
|
**Version:** 1.0.0
|
|
|
|
---
|
|
|
|
## 📋 Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Authentication](#authentication)
|
|
3. [Base URL](#base-url)
|
|
4. [Response Format](#response-format)
|
|
5. [Error Handling](#error-handling)
|
|
6. [Customers API](#customers-api)
|
|
7. [Contacts API](#contacts-api)
|
|
8. [Contact Sharing API](#contact-sharing-api)
|
|
9. [Type Definitions](#type-definitions)
|
|
10. [Usage Examples](#usage-examples)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This API provides type-safe access to the CRM system using ElysiaJS. All endpoints return consistent responses with built-in error handling.
|
|
|
|
**Key Features:**
|
|
|
|
- ✅ Type-safe with TypeScript
|
|
- ✅ Bearer token authentication via Keycloak
|
|
- ✅ Consistent response format
|
|
- ✅ Automatic error handling
|
|
- ✅ Multi-tenant support (branch-scoped)
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
All API endpoints require authentication via Bearer token from Keycloak.
|
|
|
|
### How it works:
|
|
|
|
1. Front-end authenticates with Keycloak
|
|
2. Token is stored in `window.__KEYCLOAK_TOKEN__`
|
|
3. API client automatically adds `Authorization: Bearer {token}` header
|
|
4. Token refresh is handled automatically on 401 errors
|
|
|
|
### Example:
|
|
|
|
```typescript
|
|
// Token is automatically added by api-client.ts
|
|
import { apiClient } from "@/lib/api-client";
|
|
|
|
const response = await apiClient<CustomerListResponse>("/customers");
|
|
// Authorization header is added automatically
|
|
```
|
|
|
|
---
|
|
|
|
## Base URL
|
|
|
|
```
|
|
Development: http://localhost:3000/api
|
|
Production: {TBD}
|
|
```
|
|
|
|
---
|
|
|
|
## Response Format
|
|
|
|
### Success Response
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: T, // The actual data
|
|
message?: string, // Optional success message
|
|
count?: number // Optional count for list responses
|
|
}
|
|
```
|
|
|
|
### Error Response
|
|
|
|
```typescript
|
|
{
|
|
success: false,
|
|
error: string, // Error message
|
|
details?: string // Optional detailed error info
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### HTTP Status Codes
|
|
|
|
| Status | Description |
|
|
| ------ | ------------------------------------ |
|
|
| 200 | Success |
|
|
| 400 | Bad Request (invalid input) |
|
|
| 401 | Unauthorized (token expired/invalid) |
|
|
| 403 | Forbidden (insufficient permissions) |
|
|
| 404 | Not Found |
|
|
| 500 | Internal Server Error |
|
|
|
|
### Error Response Structure
|
|
|
|
```typescript
|
|
{
|
|
success: false,
|
|
error: "Customer not found or access denied",
|
|
details: "Customer ID 'xyz' does not exist"
|
|
}
|
|
```
|
|
|
|
### Handling Errors in Front-end
|
|
|
|
```typescript
|
|
try {
|
|
const response = await apiClient<CustomerResponse>("/customers/123");
|
|
|
|
if (!response.success) {
|
|
// Handle error
|
|
console.error(response.error);
|
|
return;
|
|
}
|
|
|
|
// Success - use response.data
|
|
console.log(response.data);
|
|
} catch (error) {
|
|
// Network error or unexpected error
|
|
console.error("API call failed:", error);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Customers API
|
|
|
|
### 1. Get All Customers
|
|
|
|
Get all customers for the current user's branch.
|
|
|
|
**Endpoint:** `GET /customers`
|
|
|
|
**Query Parameters:**
|
|
|
|
- `status` (optional) - Filter by status: `active`, `inactive`, `pending`
|
|
|
|
**Request:**
|
|
|
|
```typescript
|
|
GET /customers
|
|
GET /customers?status=active
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Customer[],
|
|
count: 10,
|
|
message: "Found 10 customer(s)"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { CustomerListResponse } from "@/types/api";
|
|
|
|
const response = await apiClient<CustomerListResponse>(
|
|
"/customers?status=active",
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Get Single Customer
|
|
|
|
Get a specific customer by ID.
|
|
|
|
**Endpoint:** `GET /customers/:id`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `id` (required) - Customer ID
|
|
|
|
**Request:**
|
|
|
|
```typescript
|
|
GET /customers/123e4567-e89b-12d3-a456-426614174000
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Customer
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<CustomerResponse>(`/customers/${customerId}`);
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Create Customer
|
|
|
|
Create a new customer.
|
|
|
|
**Endpoint:** `POST /customers`
|
|
|
|
**Request Body:**
|
|
|
|
```typescript
|
|
{
|
|
name: string, // Required
|
|
email: string, // Required (must be valid email)
|
|
phone: string, // Required
|
|
company: string, // Required
|
|
address: string, // Required
|
|
customerStatus?: string, // Optional
|
|
customerType?: string, // Optional
|
|
taxId?: string // Optional
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Customer,
|
|
message: "Customer created successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
import type { CreateCustomerRequest } from "@/types/api";
|
|
|
|
const newCustomer: CreateCustomerRequest = {
|
|
name: "สมชาย ใจดี",
|
|
email: "somchai@example.com",
|
|
phone: "081-234-5678",
|
|
company: "บริษัท ไทยธุรกิจ จำกัด",
|
|
address: "123 ถนนสุขุมวิท แขวงคลองตัน เขตคลองเตย กรุงเทพฯ 10110",
|
|
customerStatus: "active",
|
|
customerType: "corporate",
|
|
taxId: "0105551234567",
|
|
};
|
|
|
|
const response = await apiClient<CreateCustomerResponse>("/customers", {
|
|
method: "POST",
|
|
body: JSON.stringify(newCustomer),
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Update Customer
|
|
|
|
Update an existing customer.
|
|
|
|
**Endpoint:** `PUT /customers/:id`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `id` (required) - Customer ID
|
|
|
|
**Request Body:**
|
|
|
|
```typescript
|
|
{
|
|
name?: string,
|
|
email?: string,
|
|
phone?: string,
|
|
company?: string,
|
|
address?: string,
|
|
customerStatus?: string,
|
|
erpCustomerCode?: string // For ERP sync
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Customer,
|
|
message: "Customer updated successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const updates = {
|
|
erpCustomerCode: "ERP-001234",
|
|
};
|
|
|
|
const response = await apiClient<UpdateCustomerResponse>(
|
|
`/customers/${customerId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(updates),
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Delete Customer
|
|
|
|
Delete a customer (soft delete).
|
|
|
|
**Endpoint:** `DELETE /customers/:id`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `id` (required) - Customer ID
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Customer,
|
|
message: "Customer deleted successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<DeleteCustomerResponse>(
|
|
`/customers/${customerId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Contacts API
|
|
|
|
### 1. Get Contacts for Customer
|
|
|
|
Get all visible contacts for a customer.
|
|
|
|
**Endpoint:** `GET /customers/:customerId/contacts`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `customerId` (required) - Customer ID
|
|
|
|
**Visibility Rules:**
|
|
A contact is visible if:
|
|
|
|
1. Created by the current user, OR
|
|
2. Marked as public (`isPublic: true`), OR
|
|
3. Shared with the current user via `contact_shares`
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact[],
|
|
count: 5,
|
|
message: "Found 5 contact(s)"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<ContactListResponse>(
|
|
`/customers/${customerId}/contacts`,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Create Contact
|
|
|
|
Create a new contact for a customer.
|
|
|
|
**Endpoint:** `POST /customers/:customerId/contacts`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `customerId` (required) - Customer ID
|
|
|
|
**Request Body:**
|
|
|
|
```typescript
|
|
{
|
|
name: string, // Required
|
|
position?: string, // Optional
|
|
phone?: string, // Optional
|
|
mobile?: string, // Optional
|
|
email?: string, // Optional
|
|
isPrimary?: boolean, // Optional (default: false)
|
|
notes?: string // Optional
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact,
|
|
message: "Contact created successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const newContact: CreateContactRequest = {
|
|
name: "วิภาวี สุขสันต์",
|
|
position: "ผู้จัดการฝ่ายจัดซื้อ",
|
|
phone: "02-123-4567",
|
|
mobile: "089-876-5432",
|
|
email: "wipavi@example.com",
|
|
isPrimary: true,
|
|
notes: "Key decision maker",
|
|
};
|
|
|
|
const response = await apiClient<CreateContactResponse>(
|
|
`/customers/${customerId}/contacts`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(newContact),
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Update Contact
|
|
|
|
Update an existing contact.
|
|
|
|
**Endpoint:** `PUT /contacts/:contactId`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can update
|
|
|
|
**Request Body:**
|
|
|
|
```typescript
|
|
{
|
|
name?: string,
|
|
position?: string,
|
|
phone?: string,
|
|
mobile?: string,
|
|
email?: string,
|
|
isPrimary?: boolean,
|
|
isPublic?: boolean,
|
|
notes?: string
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact,
|
|
message: "Contact updated successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const updates = {
|
|
isPublic: true,
|
|
notes: "Shared with team",
|
|
};
|
|
|
|
const response = await apiClient<UpdateContactResponse>(
|
|
`/contacts/${contactId}`,
|
|
{
|
|
method: "PUT",
|
|
body: JSON.stringify(updates),
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Share Contact (Make Public)
|
|
|
|
Make a contact visible to all users in the branch.
|
|
|
|
**Endpoint:** `POST /contacts/:contactId/share`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can share
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact,
|
|
message: "Contact shared successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<ShareContactResponse>(
|
|
`/contacts/${contactId}/share`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Unshare Contact (Make Private)
|
|
|
|
Make a contact private (visible only to creator).
|
|
|
|
**Endpoint:** `POST /contacts/:contactId/unshare`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can unshare
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact,
|
|
message: "Contact unshared successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<UnshareContactResponse>(
|
|
`/contacts/${contactId}/unshare`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 6. Delete Contact
|
|
|
|
Delete a contact.
|
|
|
|
**Endpoint:** `DELETE /contacts/:contactId`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can delete
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact,
|
|
message: "Contact deleted successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<DeleteContactResponse>(
|
|
`/contacts/${contactId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Contact Sharing API
|
|
|
|
### 1. Share Contact with Specific User
|
|
|
|
Share a contact with a specific user (not public).
|
|
|
|
**Endpoint:** `POST /contacts/:contactId/share-with`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Request Body:**
|
|
|
|
```typescript
|
|
{
|
|
targetUserId: string, // Required - User ID to share with
|
|
notes?: string // Optional - Notes about the share
|
|
}
|
|
```
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can share
|
|
- Cannot share with yourself
|
|
- Cannot share non-existent contact
|
|
- Cannot share contact from different branch
|
|
- Duplicate share prevention
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: ContactShare,
|
|
message: "Contact shared successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const shareRequest = {
|
|
targetUserId: "user-456",
|
|
notes: "Sales lead for Q4 project",
|
|
};
|
|
|
|
const response = await apiClient<ShareContactWithUserResponse>(
|
|
`/contacts/${contactId}/share-with`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(shareRequest),
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Unshare Contact from Specific User
|
|
|
|
Remove sharing from a specific user.
|
|
|
|
**Endpoint:** `DELETE /contacts/:contactId/share/:targetUserId`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
- `targetUserId` (required) - User ID to unshare from
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can unshare
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: ContactShare,
|
|
message: "Contact unshared successfully"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<UnshareContactFromUserResponse>(
|
|
`/contacts/${contactId}/share/${targetUserId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Get Contact Shares
|
|
|
|
Get all shares for a contact.
|
|
|
|
**Endpoint:** `GET /contacts/:contactId/shares`
|
|
|
|
**Path Parameters:**
|
|
|
|
- `contactId` (required) - Contact ID
|
|
|
|
**Rules:**
|
|
|
|
- Only the contact creator can view shares
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: ContactShare[],
|
|
count: 3,
|
|
message: "Found 3 share(s)"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const response = await apiClient<ContactShareListResponse>(
|
|
`/contacts/${contactId}/shares`,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Get Contacts Shared With Me
|
|
|
|
Get all contacts that have been shared with the current user.
|
|
|
|
**Endpoint:** `GET /contacts/shared-with-me`
|
|
|
|
**Query Parameters:**
|
|
|
|
- `customerId` (optional) - Filter by customer ID
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
{
|
|
success: true,
|
|
data: Contact[],
|
|
count: 5,
|
|
message: "Found 5 contact(s) shared with you"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
// Get all contacts shared with me
|
|
const response1 = await apiClient<ContactListResponse>(
|
|
"/contacts/shared-with-me",
|
|
);
|
|
|
|
// Filter by customer
|
|
const response2 = await apiClient<ContactListResponse>(
|
|
`/contacts/shared-with-me?customerId=${customerId}`,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Type Definitions
|
|
|
|
All API types are exported from `@/types/api`.
|
|
|
|
### Import Example:
|
|
|
|
```typescript
|
|
import type {
|
|
Customer,
|
|
Contact,
|
|
ContactShare,
|
|
CustomerListResponse,
|
|
CreateCustomerRequest,
|
|
UpdateContactRequest,
|
|
ApiResponse,
|
|
} from "@/types/api";
|
|
```
|
|
|
|
### Available Types:
|
|
|
|
- **Customer Types:** `Customer`, `CreateCustomerRequest`, `UpdateCustomerRequest`
|
|
- **Contact Types:** `Contact`, `CreateContactRequest`, `UpdateContactRequest`
|
|
- **Share Types:** `ContactShare`, `ShareContactRequest`
|
|
- **Response Types:** `SuccessResponse<T>`, `ErrorResponse`, `ApiResponse<T>`
|
|
- **List Responses:** `CustomerListResponse`, `ContactListResponse`, `ContactShareListResponse`
|
|
- **Single Item Responses:** `CustomerResponse`, `ContactResponse`, `ContactShareResponse`
|
|
- **Operation Responses:** `CreateCustomerResponse`, `UpdateCustomerResponse`, etc.
|
|
|
|
---
|
|
|
|
## Usage Examples
|
|
|
|
### Example 1: Fetch and Display Customers
|
|
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { CustomerListResponse, Customer } from "@/types/api";
|
|
|
|
export default function CustomerList() {
|
|
const [customers, setCustomers] = useState<Customer[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
async function fetchCustomers() {
|
|
try {
|
|
const response = await apiClient<CustomerListResponse>("/customers?status=active");
|
|
|
|
if (response.success) {
|
|
setCustomers(response.data);
|
|
} else {
|
|
setError(response.error);
|
|
}
|
|
} catch (err) {
|
|
setError("Failed to fetch customers");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchCustomers();
|
|
}, []);
|
|
|
|
if (loading) return <div>Loading...</div>;
|
|
if (error) return <div>Error: {error}</div>;
|
|
|
|
return (
|
|
<ul>
|
|
{customers.map(customer => (
|
|
<li key={customer.id}>
|
|
{customer.name} - {customer.company}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Example 2: Create New Customer with Form
|
|
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { CreateCustomerRequest, CreateCustomerResponse } from "@/types/api";
|
|
|
|
export default function CreateCustomerForm() {
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState(false);
|
|
|
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
setError(null);
|
|
setSuccess(false);
|
|
|
|
const formData = new FormData(e.currentTarget);
|
|
|
|
const request: CreateCustomerRequest = {
|
|
name: formData.get("name") as string,
|
|
email: formData.get("email") as string,
|
|
phone: formData.get("phone") as string,
|
|
company: formData.get("company") as string,
|
|
address: formData.get("address") as string,
|
|
customerStatus: formData.get("status") as string || undefined
|
|
};
|
|
|
|
try {
|
|
const response = await apiClient<CreateCustomerResponse>("/customers", {
|
|
method: "POST",
|
|
body: JSON.stringify(request)
|
|
});
|
|
|
|
if (response.success) {
|
|
setSuccess(true);
|
|
e.currentTarget.reset();
|
|
} else {
|
|
setError(response.error);
|
|
}
|
|
} catch (err) {
|
|
setError("Failed to create customer");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<input name="name" placeholder="Name" required />
|
|
<input name="email" type="email" placeholder="Email" required />
|
|
<input name="phone" placeholder="Phone" required />
|
|
<input name="company" placeholder="Company" required />
|
|
<input name="address" placeholder="Address" required />
|
|
<select name="status">
|
|
<option value="">Select status</option>
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
<option value="pending">Pending</option>
|
|
</select>
|
|
|
|
<button type="submit" disabled={loading}>
|
|
{loading ? "Creating..." : "Create Customer"}
|
|
</button>
|
|
|
|
{error && <div className="error">{error}</div>}
|
|
{success && <div className="success">Customer created successfully!</div>}
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Example 3: Share Contact with User
|
|
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { ShareContactWithUserResponse } from "@/types/api";
|
|
|
|
interface ShareContactModalProps {
|
|
contactId: string;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function ShareContactModal({ contactId, onClose }: ShareContactModalProps) {
|
|
const [targetUserId, setTargetUserId] = useState("");
|
|
const [notes, setNotes] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
async function handleShare() {
|
|
if (!targetUserId) {
|
|
setError("Please select a user");
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await apiClient<ShareContactWithUserResponse>(
|
|
`/contacts/${contactId}/share-with`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ targetUserId, notes })
|
|
}
|
|
);
|
|
|
|
if (response.success) {
|
|
onClose(); // Close modal on success
|
|
} else {
|
|
setError(response.error);
|
|
}
|
|
} catch (err) {
|
|
setError("Failed to share contact");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="modal">
|
|
<h2>Share Contact</h2>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="User ID"
|
|
value={targetUserId}
|
|
onChange={(e) => setTargetUserId(e.target.value)}
|
|
/>
|
|
|
|
<textarea
|
|
placeholder="Notes (optional)"
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
/>
|
|
|
|
<button onClick={handleShare} disabled={loading}>
|
|
{loading ? "Sharing..." : "Share"}
|
|
</button>
|
|
|
|
<button onClick={onClose}>Cancel</button>
|
|
|
|
{error && <div className="error">{error}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Example 4: Get Contacts Shared With Me
|
|
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { ContactListResponse, Contact } from "@/types/api";
|
|
|
|
export default function SharedContactsList() {
|
|
const [contacts, setContacts] = useState<Contact[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchSharedContacts() {
|
|
try {
|
|
const response = await apiClient<ContactListResponse>("/contacts/shared-with-me");
|
|
|
|
if (response.success) {
|
|
setContacts(response.data);
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to fetch shared contacts");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchSharedContacts();
|
|
}, []);
|
|
|
|
if (loading) return <div>Loading...</div>;
|
|
|
|
return (
|
|
<div>
|
|
<h2>Contacts Shared With Me</h2>
|
|
|
|
{contacts.length === 0 ? (
|
|
<p>No contacts shared with you yet.</p>
|
|
) : (
|
|
<ul>
|
|
{contacts.map(contact => (
|
|
<li key={contact.id}>
|
|
<strong>{contact.name}</strong>
|
|
{contact.position && <span> - {contact.position}</span>}
|
|
{contact.email && <span> ({contact.email})</span>}
|
|
<br />
|
|
<small>Shared by user</small>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Handle Errors
|
|
|
|
```typescript
|
|
try {
|
|
const response = await apiClient<SomeResponse>("/endpoint");
|
|
|
|
if (!response.success) {
|
|
// Handle API error
|
|
console.error(response.error);
|
|
return;
|
|
}
|
|
|
|
// Success
|
|
} catch (error) {
|
|
// Handle network error
|
|
console.error("Network error:", error);
|
|
}
|
|
```
|
|
|
|
### 2. Use Type Guards
|
|
|
|
```typescript
|
|
function isSuccess<T>(
|
|
response: ApiResponse<T>,
|
|
): response is SuccessResponse<T> {
|
|
return response.success === true;
|
|
}
|
|
|
|
if (isSuccess(response)) {
|
|
// TypeScript knows response is SuccessResponse
|
|
console.log(response.data);
|
|
}
|
|
```
|
|
|
|
### 3. Cache Data with React Query
|
|
|
|
```typescript
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { apiClient } from "@/lib/api-client";
|
|
import type { CustomerListResponse } from "@/types/api";
|
|
|
|
function useCustomers(status?: string) {
|
|
return useQuery({
|
|
queryKey: ["customers", status],
|
|
queryFn: () =>
|
|
apiClient<CustomerListResponse>(
|
|
`/customers${status ? `?status=${status}` : ""}`,
|
|
),
|
|
});
|
|
}
|
|
```
|
|
|
|
### 4. Optimistic Updates
|
|
|
|
```typescript
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
|
function useUpdateCustomer() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: string; data: UpdateCustomerRequest }) =>
|
|
apiClient<UpdateCustomerResponse>(`/customers/${id}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(data),
|
|
}),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["customers"] });
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
For questions or issues:
|
|
|
|
1. Check this documentation
|
|
2. Check `@/types/api.ts` for type definitions
|
|
3. Check `contact-sharing-implementation-summary.md` for contact sharing details
|
|
4. Contact the backend team
|
|
|
|
---
|
|
|
|
**End of API Reference**
|