diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644
index 0000000..c873209
--- /dev/null
+++ b/.stylelintrc.json
@@ -0,0 +1,12 @@
+{
+ "extends": ["stylelint-config-standard"],
+ "rules": {
+ "at-rule-no-unknown": [
+ true,
+ {
+ "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen", "layer"]
+ }
+ ],
+ "declaration-property-value-no-unknown": null
+ }
+}
\ No newline at end of file
diff --git a/Get Api's.txt b/Get Api's.txt
new file mode 100644
index 0000000..ca70c6e
--- /dev/null
+++ b/Get Api's.txt
@@ -0,0 +1,184 @@
+π€ Users & Authorization GET APIs
+http
+
+
+# Get all users (Web)
+https://fiesta.nearle.app/live/api/v1/web/users/getallusers?roleid=2&tenantid=8&pageno=1&pagesize=10&keyword=john
+# Get a specific user profile by ID (Web)
+https://fiesta.nearle.app/live/api/v1/web/users/getusers?userid=12
+# Get a specific user profile by ID (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/users/getusers?userid=15
+π₯ Customer Management GET APIs
+http
+
+
+# Fetch customer profile by ID or Contact (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/customers/getbyid?customerid=4082&contactno=9876543210
+# Get customer saved address locations (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/customers/getcustomerlocation?customerid=4082
+# Get customer logged support requests (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/customers/getcustomerrequests?customerid=4082&pageno=1&pagesize=10
+# Search customer names under a tenant (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/customers/search?keyword=Jane&tenantid=8
+# Retrieve customers linked to a tenant location (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/customers/gettenantcustomers?tenantid=8&locationid=2&pageno=1&pagesize=10
+# Retrieve merchant customers list (Web)
+https://fiesta.nearle.app/live/api/v1/web/customers/gettenantcustomers?tenantid=8&locationid=2&pageno=1&pagesize=10&keyword=jane
+π¦ Product & Catalog GET APIs
+http
+
+
+# Get product subcategories (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getproductsubcategories?categoryid=2&tenantid=8
+# Get products stock counts (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getproductscount?tenantid=8&categoryid=2&subcategoryid=12&approve=1
+# Get all global categories (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getproductcategories
+# Get specific product variants (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getproductvariants?tenantid=8&subcategoryid=12
+# Get master catalog listings (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getcatalougeproducts?tenantid=8&locationid=2&subcategoryid=12&keyword=&pageno=1&pagesize=10
+# Get live stocks catalog (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getproductstocks?tenantid=8&locationid=2
+# Get dynamic stock statement ledger (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getstockstatement?tenantid=8&locationid=2&subcategoryid=12&pageno=1&pagesize=10&keyword=
+# Get outlet geofenced inventory (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getlocationproducts?tenantid=8&locationid=2&subcategoryid=12&pageno=1&pagesize=10
+# Get outlet category inventory summary (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getlocationproductsummary?tenantid=8&locationid=2
+# Master products search board (Web)
+https://fiesta.nearle.app/live/api/v1/web/products/getallproducts?tenantid=8&locationid=2&keyword=milk&pageno=1&pagesize=10
+# Get product details by variant ID (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/products/getproductbyvariant?tenantid=8&variantid=4
+# Get product subcategories (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/products/getproductsubcategories?categoryid=2&tenantid=8
+# Search product catalog (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/products/getallproducts?keyword=milk&pageno=1&pagesize=10
+# Get structured home subcategory list (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/products/getproductsbysubcategory?categoryid=2&tenantid=8&locationid=2
+# Get mobile geofenced outlet products (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/products/getlocationproducts?tenantid=8&locationid=2&pageno=1&pagesize=20
+π Order Orchestration GET APIs
+http
+
+
+# Filtered dynamic orders board (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/getorders?tenantid=8&locationid=2&status=processing&pageno=1&pagesize=10
+# Tenant orders board (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/tenant/getorders?tenantid=8&locationid=2&status=processing&pageno=1&pagesize=10
+# Partner fleet orders board (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/partner/getorders?partnerid=1&status=processing&pageno=1&pagesize=10
+# Individual consumer invoice histories (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/customer/getorders?customerid=4082&status=delivered&pageno=1&pagesize=10
+# Operator/User orders board (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/user/getorders?appuserid=12&status=processing&pageno=1&pagesize=10
+# System Admin orders board (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/admin/getorders?applocationid=1&status=processing&pageno=1&pagesize=10
+# Get order dashboard stats summary (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/getordersummary?tenantid=8&fromdate=2026-05-01&todate=2026-05-20
+# Get location orders summary (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/getlocationsummary?tenantid=8
+# Get annual orders insights analytics (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/getorderinsight?tenantid=8
+# Get order detailed lines (Web)
+https://fiesta.nearle.app/live/api/v1/web/orders/getorderdetails?orderheaderid=2099
+# Get customer order history (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/orders/getcustomerorders?customerid=4082&pageno=1&pagesize=10
+# Get specific tenant store orders (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/orders/tenant/getorders?tenantid=8&locationid=2&pageno=1&pagesize=10
+# Get mobile order detailed lines (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/orders/getorderdetails?orderheaderid=2099
+ποΈ Partner & Rider GET APIs
+http
+
+
+# Get active riders (Web)
+https://fiesta.nearle.app/live/api/v1/web/partners/getriders?partnerid=1&applocationid=1&userid=12&tenantid=8
+# Get partner profiles (Web)
+https://fiesta.nearle.app/live/api/v1/web/partners/getpartners?partnerid=1&applocationid=1&userid=12
+# Get rider shifts (Web)
+https://fiesta.nearle.app/live/api/v1/web/partners/getridershifts?applocationid=1
+# Get location configurations (Web)
+https://fiesta.nearle.app/live/api/v1/web/partners/getlocations?userid=12&configid=1
+# Get rider log sheet (Web)
+https://fiesta.nearle.app/live/api/v1/web/partners/getriderlogs?partnerid=1&applocationid=1&fromdate=2026-05-20&todate=2026-05-20
+# Get partner profiles (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/partners/getpartners?partnerid=1&applocationid=1&userid=12
+# Get rider log sheet (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/partners/getriderlogs?partnerid=1&applocationid=1&fromdate=2026-05-20&todate=2026-05-20
+# Get rider operational info (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/partners/getriderinfo?userid=15
+# Get active riders list (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/partners/getriders?partnerid=1&applocationid=1&userid=15&tenantid=8
+π Logistics & Deliveries GET APIs
+http
+
+
+# Get deliveries performance summaries (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/deliverysummary?tenantid=8&partnerid=1&userid=12&applocationid=1&locationid=2&fromdate=2026-05-20&todate=2026-05-20
+# Get daily delivery insights (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/getdeliveryinsight?tenantid=8
+# Get location deliveries summary (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/getlocationsummary?tenantid=8
+# Get deliveries financial report summary (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/getreportsummary?tenantid=8&partnerid=1&userid=12&applocationid=1&fromdate=2026-05-01&todate=2026-05-20
+# Get fleet rider summary metrics (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/getridersummary?applocationid=1&partnerid=1&tenantid=8&fromdate=2026-05-20&todate=2026-05-20
+# Get master deliveries board (Web)
+https://fiesta.nearle.app/live/api/v1/web/deliveries/getdeliveries?tenantid=8&fromdate=2026-05-20&todate=2026-05-20
+# Get mobile dispatch summaries (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/deliveries/deliverysummary?userid=15&fromdate=2026-05-20&todate=2026-05-20
+# Get mobile deliveries board (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/deliveries/getdeliveries?userid=15&status=assigned
+# Fetch rider active shift deliveries queue (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/deliveries/getdeliveryqueues?userid=15&fromdate=2026-05-20&todate=2026-05-20
+πͺ Tenant & Outlet Location GET APIs
+http
+
+
+# Search registered tenants (Web)
+https://fiesta.nearle.app/live/api/v1/web/tenants/search?status=Active&keyword=Fresh
+# Search tenants by keyword (Web)
+https://fiesta.nearle.app/live/api/v1/web/tenants/searchbykeyword?keyword=daily
+# Get all active tenants catalog (Web)
+https://fiesta.nearle.app/live/api/v1/web/tenants/getalltenants?applocationid=1&status=Active&pageno=1&pagesize=10
+# Get outlet locations assigned to a tenant (Web)
+https://fiesta.nearle.app/live/api/v1/web/tenants/gettenantlocations?tenantid=8
+# Retrieve delivery time slots config (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/gettenantslot
+# Search tenants by keyword (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/searchbykeyword?keyword=grocery
+# Retrieve tenants associated with a customer (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/getcustomertenants?customerid=4082&tenant=0
+# Get outlet locations linked to a tenant (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/gettenantlocations?tenantid=8
+# Get logistics pricing slabs for a tenant (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/gettenantpricing?tenantid=8&applocationid=1
+# Get staff members under a tenant store (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/getstaffs?tenantid=8
+# Get tenant detailed profile info (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/tenants/gettenantinfo?tenantid=8&locationid=2
+βοΈ Utilities & General Configurations GET APIs
+http
+
+
+# Fetch application categories by tag (Web)
+https://fiesta.nearle.app/live/api/v1/web/utils/getapptypes?tag=customer
+# Resolve subcategories (Web)
+https://fiesta.nearle.app/live/api/v1/web/utils/getsubcategories?moduleid=1&categoryid=2
+# Fetch system active geofence details (Web)
+https://fiesta.nearle.app/live/api/v1/web/utils/getapplocations?applocationid=1
+# Fetch global system categories configurations (Web)
+https://fiesta.nearle.app/live/api/v1/web/utils/getappcategories
+# Get mobile geofence configuration details (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getapplocationconfig?applocationid=1
+# Get mobile active geofence details (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getapplocations?applocationid=1
+# Fetch mobile app types by tag (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getapptypes?tag=rider
+# Fetch global payment & geofence configs (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getappconfig?configid=1
+# Get mobile category subcategories list (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getsubcategories?moduleid=1&categoryid=2
+# Fetch mobile app categories configurations (Mobile)
+https://fiesta.nearle.app/live/api/v1/mob/utils/getappcategories
diff --git a/Post, put and delete Api's.txt b/Post, put and delete Api's.txt
new file mode 100644
index 0000000..3aa7077
--- /dev/null
+++ b/Post, put and delete Api's.txt
@@ -0,0 +1,652 @@
+1. User & Authentication APIs
+π» Web Portal
+POST Tenant Web Panel Login
+URL: https://fiesta.nearle.app/live/api/v1/web/users/tenant/weblogin
+Payload:
+json
+
+
+{
+ "authname": "merchant_admin_01",
+ "password": "PasswordSecurityHash99!"
+}
+POST General Application Login
+URL: https://fiesta.nearle.app/live/api/v1/web/users/applogin
+Payload:
+json
+
+
+{
+ "authname": "system_operator",
+ "password": "OperatorSafePasswordSecret",
+ "deviceid": "device_uuid_8828b",
+ "devicetype": "android"
+}
+POST Register New Web Staff Account
+URL: https://fiesta.nearle.app/live/api/v1/web/users/create
+Payload:
+json
+
+
+{
+ "authname": "tenant_staff_steve",
+ "firstname": "Steve",
+ "lastname": "Rogers",
+ "password": "SteveSecurePassword12",
+ "email": "steve.rogers@merchant.com",
+ "dialcode": "+61",
+ "contactno": "412345678",
+ "roleid": 3,
+ "pin": 1234,
+ "address": "100 Flinders St",
+ "suburb": "Melbourne",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3000",
+ "tenantid": 8,
+ "locationid": 2,
+ "applocationid": 1,
+ "status": "active"
+}
+PUT Update Web Staff User Details
+URL: https://fiesta.nearle.app/live/api/v1/web/users/update
+Payload:
+json
+
+
+{
+ "userid": 15,
+ "firstname": "Steve",
+ "lastname": "Captain",
+ "email": "steve.captain@merchant.com",
+ "contactno": "412345678",
+ "address": "200 Flinders St",
+ "suburb": "Melbourne",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3000",
+ "status": "active"
+}
+π± Mobile App
+POST Rider/Merchant Mobile App Login
+URL: https://fiesta.nearle.app/live/api/v1/mob/users/tenant/login
+Payload:
+json
+
+
+{
+ "authname": "rider_john_01",
+ "password": "RiderSecretKey"
+}
+POST Create Mobile Staff User
+URL: https://fiesta.nearle.app/live/api/v1/mob/users/create
+Payload: (Uses the same schema as Web Register New Web Staff Account)
+PUT Update Mobile Staff Details
+URL: https://fiesta.nearle.app/live/api/v1/mob/users/update
+Payload: (Uses the same schema as Web Update Web Staff User Details)
+2. Customer Management APIs
+π± Mobile App
+POST Passwordless OTP Login (via Phone)
+URL: https://fiesta.nearle.app/live/api/v1/mob/customers/login
+Payload:
+json
+
+
+{
+ "contactno": "0499888777"
+}
+POST Register Customer Account
+URL: https://fiesta.nearle.app/live/api/v1/mob/customers/create
+Payload:
+json
+
+
+{
+ "firstname": "Jane",
+ "lastname": "Smith",
+ "profileimage": "https://storage.nearle.app/avatars/jane_smith.jpg",
+ "gender": "Female",
+ "dob": "1994-11-20",
+ "dialcode": "+61",
+ "contactno": "499888777",
+ "email": "jane.smith@gmail.com",
+ "deviceid": "uuid_7728b991a",
+ "devicetype": "ios",
+ "authmode": 1,
+ "customertoken": "fcm_token_device_hash_xyz",
+ "address": "456 Oak Avenue",
+ "suburb": "Richmond",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3121",
+ "latitude": "-37.8212",
+ "longitude": "144.9984",
+ "applocationid": 1,
+ "status": 1,
+ "intro": "Regular subscriber of organic morning deliveries."
+}
+POST Save New Geofenced Location Address
+URL: https://fiesta.nearle.app/live/api/v1/mob/customers/createlocations
+Payload:
+json
+
+
+{
+ "customerid": 4082,
+ "address": "123 High Street",
+ "suburb": "Prahran",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3181",
+ "latitude": "-37.8502",
+ "longitude": "144.9924",
+ "primaryaddress": 1,
+ "status": 1
+}
+POST Log Customer Support Ticket Request
+URL: https://fiesta.nearle.app/live/api/v1/mob/customers/createcustomerrequest
+Payload:
+json
+
+
+{
+ "customerid": 4082,
+ "tenantid": 8,
+ "apptypeid": 1,
+ "locationid": 2,
+ "subject": "Delay in Morning Milk Delivery",
+ "remarks": "Order scheduled for 7:00 AM hasn't arrived.",
+ "status": 1
+}
+PUT Update Customer Profile Details
+URL: https://fiesta.nearle.app/live/api/v1/mob/customers/update
+Payload:
+json
+
+
+{
+ "customerid": 4082,
+ "firstname": "Jane",
+ "lastname": "Miller",
+ "email": "jane.miller@gmail.com",
+ "contactno": "499888777",
+ "address": "789 Pine Road",
+ "suburb": "Hawthorn",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3122",
+ "status": 1
+}
+3. Catalog & Product Inventory APIs
+π» Web Portal
+POST Add Multi-Product Stock Entry
+URL: https://fiesta.nearle.app/live/api/v1/web/products/createproductstock
+Payload:
+json
+
+
+[
+ {
+ "tenantid": 8,
+ "locationid": 2,
+ "productid": 105,
+ "quantity": 150,
+ "stocktype": "credit",
+ "status": "active"
+ },
+ {
+ "tenantid": 8,
+ "locationid": 2,
+ "productid": 109,
+ "quantity": 80,
+ "stocktype": "credit",
+ "status": "active"
+ }
+]
+POST Create Master Product Catalog Item
+URL: https://fiesta.nearle.app/live/api/v1/web/products/create
+Payload:
+json
+
+
+{
+ "applocationid": 1,
+ "tenantid": 8,
+ "categoryid": 2,
+ "subcategoryid": 12,
+ "productname": "Fresh Cow Milk 1L",
+ "productimage": "https://storage.nearle.app/products/cow_milk_1l.png",
+ "productdesc": "Pure farm fresh cow milk pasteurized.",
+ "productsku": "MILK-COW-1L",
+ "productunit": "Litre",
+ "unitvalue": "1",
+ "productcost": 1.80,
+ "retailprice": 3.50,
+ "taxpercent": 5.00,
+ "productstock": 100,
+ "productstatus": "available",
+ "approve": 1
+}
+POST Map Product to Specific Outlet Location
+URL: https://fiesta.nearle.app/live/api/v1/web/products/createproductlocation
+Payload:
+json
+
+
+[
+ {
+ "tenantid": 8,
+ "locationid": 2,
+ "productid": 105,
+ "catlougeid": 1,
+ "minquantity": 5,
+ "maxquantity": 200,
+ "price": 3.75,
+ "status": "Active"
+ }
+]
+POST Create Product Variant Metadata
+URL: https://fiesta.nearle.app/live/api/v1/web/products/createproductvariant
+Payload:
+json
+
+
+{
+ "tenantid": 8,
+ "variantname": "1.5 Liters Bottle",
+ "categoryid": 2,
+ "subcategoryid": 12,
+ "status": "active"
+}
+PUT Update Master Product Details
+URL: https://fiesta.nearle.app/live/api/v1/web/products/update
+Payload:
+json
+
+
+{
+ "productid": 105,
+ "productname": "Organic Farm Cow Milk 1L",
+ "productcost": 1.95,
+ "retailprice": 3.75,
+ "productstock": 120,
+ "productstatus": "available"
+}
+PUT Update Product Outlet Constraints
+URL: https://fiesta.nearle.app/live/api/v1/web/products/updateproductlocation
+Payload:
+json
+
+
+{
+ "productlocationid": 25,
+ "tenantid": 8,
+ "locationid": 2,
+ "productid": 105,
+ "minquantity": 10,
+ "maxquantity": 150,
+ "price": 3.99,
+ "status": "Active"
+}
+DELETE Purge Master Product Catalog Entry
+URL: https://fiesta.nearle.app/live/api/v1/web/products/delete?productid=105
+Parameters:
+productid (int, required): Unique ID of the catalog item to be purged.
+π± Mobile App
+PUT Mobile Update Product Details
+URL: https://fiesta.nearle.app/live/api/v1/mob/products/update
+Payload: (Uses the same schema as Web Update Master Product Details)
+PUT Mobile Update Product Outlet Config
+URL: https://fiesta.nearle.app/live/api/v1/mob/products/updateproductlocation
+Payload: (Uses the same schema as Web Update Product Outlet Constraints)
+4. Order Orchestration APIs
+π» Web Portal
+POST Create New Web Order (Flat JSON Format)
+URL: https://fiesta.nearle.app/live/api/v1/web/orders/createorder
+Payload:
+json
+
+
+{
+ "tenantid": 8,
+ "locationid": 2,
+ "applocationid": 1,
+ "moduleid": 1,
+ "customerid": 4082,
+ "orderstatus": "pending",
+ "deliverytype": "standard",
+ "deliverytime": "2026-05-20 18:00:00",
+ "pickupaddress": "Shop 4, Central Plaza, Melbourne",
+ "pickuplat": "-37.8136",
+ "pickuplong": "144.9631",
+ "pickupcustomer": "Central Plaza Merchant",
+ "pickupcontactno": "399887766",
+ "deliveryaddress": "Apt 4B, Sunset Boulevard, Richmond",
+ "deliverylat": "-37.8212",
+ "deliverylong": "144.9984",
+ "orderamount": 11.48,
+ "taxamount": 1.10,
+ "ordervalue": 12.58,
+ "itemcount": 2,
+ "paymenttype": 1,
+ "paymentstatus": 0,
+ "deliverycharge": 3.00,
+ "ordernotes": "Please ring doorbell twice.",
+ "items": [
+ {
+ "productid": 105,
+ "productname": "Organic Whole Milk 1L",
+ "orderqty": 2,
+ "price": 3.99,
+ "taxpercentage": 10.00,
+ "taxamount": 0.80,
+ "productsumprice": 7.98
+ },
+ {
+ "productid": 109,
+ "productname": "Salted Butter 250g",
+ "orderqty": 1,
+ "price": 3.50,
+ "taxpercentage": 10.00,
+ "taxamount": 0.35,
+ "productsumprice": 3.50
+ }
+ ]
+}
+PUT Update Order Status & Financial Flag
+URL: https://fiesta.nearle.app/live/api/v1/web/orders/updateorder
+Payload:
+json
+
+
+{
+ "orderheaderid": 2099,
+ "orderstatus": "ready",
+ "paymentstatus": 1,
+ "remarks": "Order packed and waiting for rider assignment."
+}
+π± Mobile App
+POST Create Mobile Order (Wrapped JSON Format)
+URL: https://fiesta.nearle.app/live/api/v1/mob/orders/createorder
+Payload:
+json
+
+
+{
+ "orders": {
+ "tenantid": 8,
+ "locationid": 2,
+ "applocationid": 1,
+ "moduleid": 1,
+ "customerid": 4082,
+ "orderstatus": "pending",
+ "deliverytype": "standard",
+ "deliverytime": "2026-05-20 18:00:00",
+ "pickupaddress": "Shop 4, Central Plaza, Melbourne",
+ "pickuplat": "-37.8136",
+ "pickuplong": "144.9631",
+ "pickupcustomer": "Central Plaza Merchant",
+ "pickupcontactno": "399887766",
+ "deliveryaddress": "Apt 4B, Sunset Boulevard, Richmond",
+ "deliverylat": "-37.8212",
+ "deliverylong": "144.9984",
+ "orderamount": 11.48,
+ "taxamount": 1.10,
+ "ordervalue": 12.58,
+ "itemcount": 2,
+ "paymenttype": 1,
+ "paymentstatus": 0,
+ "deliverycharge": 3.00,
+ "ordernotes": "Leave in parcel locker.",
+ "items": [
+ {
+ "productid": 105,
+ "productname": "Organic Whole Milk 1L",
+ "orderqty": 2,
+ "price": 3.99,
+ "taxpercentage": 10.00,
+ "taxamount": 0.80,
+ "productsumprice": 7.98
+ },
+ {
+ "productid": 109,
+ "productname": "Salted Butter 250g",
+ "orderqty": 1,
+ "price": 3.50,
+ "taxpercentage": 10.00,
+ "taxamount": 0.35,
+ "productsumprice": 3.50
+ }
+ ]
+ }
+}
+PUT Mobile Update Order Status
+URL: https://fiesta.nearle.app/live/api/v1/mob/orders/updateorder
+Payload: (Uses the same schema as Web Update Order Status & Financial Flag)
+5. Delivery & Rider Logistics APIs
+π» Web Portal
+POST Initialize Logistics Delivery Dispatch (Assign Rider)
+URL: https://fiesta.nearle.app/live/api/v1/web/deliveries/createdeliveries
+Payload:
+json
+
+
+[
+ {
+ "orderheaderid": 2100,
+ "applocationid": 1,
+ "partnerid": 1,
+ "tenantid": 8,
+ "moduleid": 1,
+ "locationid": 2,
+ "userid": 15,
+ "orderid": "ORD-19028-4",
+ "deliverydate": "2026-05-20",
+ "orderstatus": "assigned",
+ "assigntime": "2026-05-20 12:10:00",
+ "itemcount": 2,
+ "orderamount": 12.58,
+ "customerid": 4082,
+ "pickupcustomer": "Central Merchant Warehouse",
+ "pickupcontactno": "987654321",
+ "pickuplocationid": 2,
+ "pickupaddress": "Shop 4, Central Plaza, Melbourne",
+ "pickuplat": "-37.8136",
+ "pickuplon": "144.9631",
+ "deliverycustomerid": 4082,
+ "deliverylocationid": 554,
+ "deliverycustomer": "Jane Smith",
+ "deliverycontactno": "499888777",
+ "deliveryaddress": "456 Oak Avenue, Richmond, VIC, 3121",
+ "droplat": "-37.8212",
+ "droplon": "144.9984",
+ "deliverycharges": 3.00,
+ "deliveryamt": 15.58,
+ "deliverytype": "standard",
+ "paymenttype": 1
+ }
+]
+PUT Update Rider Pickup Status
+URL: https://fiesta.nearle.app/live/api/v1/web/deliveries/updatedelivery
+Payload:
+json
+
+
+{
+ "deliveryid": 8871,
+ "orderheaderid": 2100,
+ "userid": 15,
+ "orderstatus": "picked",
+ "pickuptime": "2026-05-20 12:20:00",
+ "riderslat": "-37.8140",
+ "riderslon": "144.9640"
+}
+π± Mobile App
+POST Initialize Mobile Logistics Delivery
+URL: https://fiesta.nearle.app/live/api/v1/mob/deliveries/createdeliveries
+Payload: (Uses the same schema as Web Initialize Logistics Delivery Dispatch)
+PUT Rider Update Dispatch Status (Delivered & GPS Tracking)
+URL: https://fiesta.nearle.app/live/api/v1/mob/deliveries/updatedelivery
+Payload:
+json
+
+
+{
+ "deliveryid": 8871,
+ "orderheaderid": 2100,
+ "userid": 15,
+ "orderstatus": "delivered",
+ "deliverytime": "2026-05-20 12:45:00",
+ "riderslat": "-37.8210",
+ "riderslon": "144.9980",
+ "actualkms": "4.2",
+ "feedback": "Handed over safely.",
+ "smsdelivery": 1
+}
+6. Tenant & Location Configuration APIs
+π» Web Portal
+POST Link Customer Profile to Tenant Store
+URL: https://fiesta.nearle.app/live/api/v1/web/tenants/createtenantcustomer
+Payload:
+json
+
+
+{
+ "moduleid": 1,
+ "tenantid": 8,
+ "locationid": 2,
+ "customerid": 4082,
+ "customerlocationid": 554,
+ "status": 1
+}
+POST Create New Geofenced Store Location
+URL: https://fiesta.nearle.app/live/api/v1/web/tenants/createlocation
+Payload:
+json
+
+
+{
+ "tenantid": 8,
+ "applocationid": 1,
+ "moduleid": 1,
+ "locationname": "Hawthorn Daily Fresh Store",
+ "email": "hawthorn.store@merchant.com",
+ "contactno": "399443322",
+ "latitude": "-37.8222",
+ "longitude": "145.0384",
+ "address": "12 Glenferrie Rd",
+ "suburb": "Hawthorn",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3122",
+ "opentime": "07:00:00",
+ "closetime": "22:00:00",
+ "partnerid": 1,
+ "deliveryradius": 5000,
+ "deliverymins": 30,
+ "cancelsecs": 60,
+ "status": "Active"
+}
+POST Map Tenant Location Linkage Entry
+URL: https://fiesta.nearle.app/live/api/v1/web/tenants/createtenantlocation
+Payload: (Uses the same schema as Create New Geofenced Store Location)
+PUT Update Store Location Configurations
+URL: https://fiesta.nearle.app/live/api/v1/web/tenants/updatelocation
+Payload:
+json
+
+
+{
+ "locationid": 2,
+ "tenantid": 8,
+ "applocationid": 1,
+ "locationname": "Richmond Daily Fresh Store",
+ "email": "richmond.store@merchant.com",
+ "contactno": "399887766",
+ "latitude": "-37.8212",
+ "longitude": "144.9984",
+ "address": "Shop 4, 100 Church St",
+ "suburb": "Richmond",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3121",
+ "opentime": "07:00:00",
+ "closetime": "22:00:00",
+ "deliveryradius": 8000,
+ "deliverymins": 45,
+ "cancelsecs": 120,
+ "status": "Active"
+}
+PUT Modify Tenant Location Linkage Config
+URL: https://fiesta.nearle.app/live/api/v1/web/tenants/updatetenantlocation
+Payload: (Uses the same schema as Update Store Location Configurations)
+π± Mobile App
+POST Mobile Link Customer Profile to Tenant Store
+URL: https://fiesta.nearle.app/live/api/v1/mob/tenants/createtenantcustomer
+Payload: (Uses the same schema as Web Link Customer Profile to Tenant Store)
+POST Mobile Create Geofenced Store Location
+URL: https://fiesta.nearle.app/live/api/v1/mob/tenants/createlocation
+Payload: (Uses the same schema as Web Create New Geofenced Store Location)
+POST Onboard/Create New Staff Member Account
+URL: https://fiesta.nearle.app/live/api/v1/mob/tenants/createstaff
+Payload: (Uses the same schema as Web Register New Web Staff Account)
+POST Onboard New Tenant & Admin Profile (Joint Creation)
+URL: https://fiesta.nearle.app/live/api/v1/mob/tenants/createtenantuser
+Payload:
+json
+
+
+{
+ "tenantname": "Fresh Organic Greens",
+ "configid": 1,
+ "partnerid": 1,
+ "moduleid": 1,
+ "tenanttype": "Enterprise",
+ "registrationno": "ABN-19028-299",
+ "tenanttoken": "tenant_fcm_token_hash_value_xyz",
+ "companyname": "Fresh Organic Greens Pty Ltd",
+ "devicetype": "web",
+ "firstname": "Arthur",
+ "primaryemail": "arthur@organicgreens.com",
+ "primarycontact": "488999000",
+ "categoryid": 2,
+ "subcategoryid": 12,
+ "address": "400 Chapel St",
+ "suburb": "South Yarra",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3141",
+ "latitude": "-37.8398",
+ "longitude": "144.9953",
+ "tenantimage": "https://storage.nearle.app/tenants/organic_greens.png",
+ "tenantinfo": "Direct farm-to-table organic vegetables delivery provider.",
+ "paymode1": 1,
+ "paymode2": 1,
+ "promotion": 0,
+ "minorder": 15,
+ "applocationid": 1,
+ "approved": 1,
+ "status": "Active",
+ "tenantlocations": {
+ "locationname": "South Yarra Main Warehouse",
+ "email": "southyarra@organicgreens.com",
+ "contactno": "488999000",
+ "latitude": "-37.8398",
+ "longitude": "144.9953",
+ "address": "400 Chapel St",
+ "suburb": "South Yarra",
+ "city": "Melbourne",
+ "state": "VIC",
+ "postcode": "3141",
+ "opentime": "06:00:00",
+ "closetime": "21:00:00",
+ "partnerid": 1,
+ "deliveryradius": 6000,
+ "deliverymins": 30,
+ "cancelsecs": 90,
+ "status": "Active"
+ }
+}
+PUT Mobile Update Store Location Configurations
+URL: https://fiesta.nearle.app/live/api/v1/mob/tenants/updatelocation
+Payload: (Uses the same schema as Web Update Store Location Configurations)
diff --git a/combine_topics.cjs b/combine_topics.cjs
new file mode 100644
index 0000000..fe9313e
--- /dev/null
+++ b/combine_topics.cjs
@@ -0,0 +1,21 @@
+const fs = require('fs');
+const path = require('path');
+const { existingTopics } = require('./original_topics.cjs');
+
+// Read current topics.js which has the new REST endpoints.
+// We'll just import it using dynamic import since it's an ES module.
+(async () => {
+ const topicsModule = await import('file:///' + path.join(__dirname, 'src/data/topics.js').replace(/\\/g, '/'));
+ const restTopics = topicsModule.topics;
+
+ const out = `export const LEGACY_BASE_URL = 'https://api.workolik.com';
+export const REST_BASE_URL = 'https://fiesta.nearle.app';
+
+export const legacyTopics = ${JSON.stringify(existingTopics, null, 2)};
+
+export const restTopics = ${JSON.stringify(restTopics, null, 2)};
+`;
+
+ fs.writeFileSync(path.join(__dirname, 'src/data/topics.js'), out, 'utf8');
+ console.log('Successfully combined legacy and rest topics into topics.js');
+})();
diff --git a/index.html b/index.html
index 29a7f22..549b3b5 100644
--- a/index.html
+++ b/index.html
@@ -5,7 +5,7 @@
-
EXpress Developer Docs
+ Nearle Express Developer Docs
diff --git a/original_topics.cjs b/original_topics.cjs
new file mode 100644
index 0000000..7f318eb
--- /dev/null
+++ b/original_topics.cjs
@@ -0,0 +1,347 @@
+const existingTopics = [
+ {
+ id: 'utils',
+ name: 'Utils',
+ description: 'Shared lookup endpoints roles, locations, configs, app types, pricing.',
+ endpoints: [
+ {
+ name: 'getuserroles',
+ method: 'GET',
+ url: '/api/rest/Xpress/utils/getuserroles',
+ description: 'Retrieve all user roles available in the system.'
+ },
+ {
+ name: 'getapptypes',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getapptypes?tag=DELIVERY',
+ description: 'List application types filtered by a tag (e.g. DELIVERY).',
+ _note: "Host typo in source ('workopi') corrected to api.workolik.com."
+ },
+ {
+ name: 'getapplocations',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getapplocations?userid=1326',
+ description: 'List all application locations.',
+ _note: "Source name 'Gpplocation' assumed to be 'Getapplocations'."
+ },
+ {
+ name: 'getapppricing',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getapppricing?applocationid=1',
+ description: 'Retrieve app pricing for a given app location.',
+ _note: "Source query 'getapppripplocationid=' reconstructed as 'getapppricing?applocationid='."
+ },
+ {
+ name: 'getallpricing',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getallpricing?applocationid=1',
+ description: 'Retrieve all pricing rules for an app location.'
+ },
+ {
+ name: 'getapplocationconfig',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getapplocationconfig',
+ description: 'Retrieve the configuration for application locations.',
+ _note: "Host typo in source ('workom') corrected to api.workolik.com."
+ },
+ {
+ name: 'getsubcategories',
+ method: 'GET',
+ url: '/api/rest/xpress/utils/getsubcategories/?moduleid=6',
+ description: 'List all subcategories for a given module.'
+ }
+ ]
+ },
+ {
+ id: 'users',
+ name: 'Users',
+ description: 'Manage users and roles across the Xpress platform.',
+ endpoints: [
+ {
+ name: 'getallusers',
+ method: 'GET',
+ url: '/api/rest/xpress/users/getallusers?roleid=2&configid=1&tenantid=1079&status=Active&keyword=%john%&limit=10&offset=0',
+ description: 'List users filtered by role, config, tenant, status, and keyword.'
+ },
+ {
+ name: 'getusersinfo',
+ method: 'GET',
+ url: '/api/rest/xpress/users/getusers?userid=1326',
+ description: 'Retrieve a single user by ID.'
+ }
+ ]
+ },
+ {
+ id: 'partners',
+ name: 'Partners',
+ description: 'Partners, riders, locations, shifts, and rider pricing.',
+ endpoints: [
+ {
+ name: 'getlocations (by user)',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getlocations?userid=1277&configid=9',
+ description: 'Get partner locations linked to a specific user.'
+ },
+ {
+ name: 'gettenantlocations ',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/gettenantlocations?keyword=daily&tenantid=916',
+ description: 'Get partner locations linked to a specific tenant.'
+ },
+ {
+ name: 'getpartners',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getpartners',
+ description: 'List all partners.',
+ _note: "Source URL had a double slash (/api/rest//partners/) β assumed /xpress/partners/."
+ },
+ {
+ name: 'getriderpricing',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getriderpricing?applocationid=0',
+ description: 'Get rider pricing rules for an app location.'
+ },
+ {
+ name: 'getallriders',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getallriders?partnerid=44&limit=10',
+ description: 'List all riders for a partner.'
+ },
+ {
+ name: 'getallridersummary',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getallridersummary?applocationid=1',
+ description: 'Aggregated summary of all riders at a location.'
+ },
+ {
+ name: 'getriderdetail',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getriderdetail?userid=1',
+ description: 'Detailed information for a specific rider.',
+ _note: "Source had 'htapi.workolik.com' (missing 'tps://') β restored to https://api.workolik.com."
+ },
+ {
+ name: 'getridershifts',
+ method: 'GET',
+ url: '/api/rest/xpress/partners/getridershifts?applocationid=1',
+ description: 'Get rider shift records for an app location.',
+ _note: "Host typo in source ('workk.com') corrected to api.workolik.com."
+ }
+ ]
+ },
+ {
+ id: 'tenants',
+ name: 'Tenants',
+ description: 'Tenant accounts info, locations, customers, orders, pricing, summary.',
+ endpoints: [
+ {
+ name: 'gettenants',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/gettenants?applocationid=1&status=active&partnerid=44',
+ description: 'List tenants filtered by app location and status.',
+ params: [
+ { name: 'applocationid', type: 'STRING', default: '1' },
+ { name: 'status', type: 'STRING', default: 'active' },
+ { name: 'partnerid', type: 'string', default: '44' }
+ ],
+ _note: "Source name 'Gettenan' truncated β assumed 'Gettenants'."
+ },
+ {
+ name: 'getalltenants',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/getalltenants?applocationid=1&moduleid=6&tenanttype=E&keyword=%25%25&status=Active&limit=10&offset=0',
+ description: 'List all tenants with extensive filters.',
+ params: [
+ { name: 'applocationid', type: 'STRING', default: '1' },
+ { name: 'moduleid', type: 'STRING', default: '6' },
+ { name: 'tenanttype', type: 'STRING', default: 'E' },
+ { name: 'keyword', type: 'STRING', default: '%%' },
+ { name: 'status', type: 'STRING', default: 'Active' },
+ { name: 'limit', type: 'STRING', default: '10' },
+ { name: 'offset', type: 'STRING', default: '0' }
+ ],
+ _note: "Source query 'status=Actilimit=10' reconstructed as 'status=Active&limit=10'. 'approved=1' param dropped per spec."
+ },
+ {
+ name: 'gettenantinfo',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/gettenantinfo?tenantid=1',
+ description: 'Get basic information about a tenant.'
+ },
+ {
+ name: 'gettenantlocations',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/gettenantlocations?tenantid=916&keyword=%%',
+ description: 'List physical locations for a tenant.'
+ },
+ {
+ name: 'gettenantsummary',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/gettenantsummary?moduleid=6&applocationid=1&tenanttype=E&keyword=%%',
+ description: 'Aggregated summary for tenants under a module/location.',
+ _note: "Source 'tenettenantsummary' reconstructed as 'tenants/gettenantsummary'."
+ },
+ {
+ name: 'getpricinglist',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/getpricinglist?tenantid=1087',
+ description: 'Retrieve the tenantβs pricing list.',
+ _note: "Source 'getpricingl' truncated β assumed 'getpricinglist'."
+ },
+ {
+ name: 'tenantsearch',
+ method: 'GET',
+ url: '/api/rest/xpress/tenants/search?keyword=daily&status=active',
+ description: 'Full-text search for tenants by keyword.',
+ _note: "Source name 'Tennatsearch' assumed to be 'Tenantsearch'."
+ },
+ {
+ name: 'getorders',
+ method: 'GET',
+ url: '/api/rest/xpress/tenant/getorders?applocationid=&tenantid=&locationid=&status=',
+ description: 'List orders scoped to a single tenant.',
+ _note: "Source uses /tenant/ (singular) here. Confirm whether this should be /tenants/ or kept as /tenant/."
+ }
+ ]
+ },
+ {
+ id: 'customers',
+ name: 'Customers',
+ description: 'Customer accounts, lookups, and search.',
+ endpoints: [
+ {
+ name: 'getallcustomers',
+ method: 'GET',
+ url: '/api/rest/xpress/customers/getallcustomers?applocationid=&keyword=&pageno=&pagesize=',
+ description: 'Paginated list of all customers under an app location.'
+ },
+ {
+ name: 'gettenantcustomers',
+ method: 'GET',
+ url: '/api/rest/xpress/customers/gettenantcustomers?tenantid=1087&limit=10&offset=0',
+ description: 'List customers under a specific tenant.',
+ _note: "Source path 'xpresustomers' reconstructed as 'xpress/customers'."
+ },
+ {
+ name: 'searchcustomers',
+ method: 'GET',
+ url: '/api/rest/xpress/customers/searchcustomers?tenantid=1087&keyword=%%',
+ description: 'Search customers by keyword within a tenant.',
+ _note: "Source name 'Searchcumer' assumed to be 'Searchcustomers'."
+ }
+ ]
+ },
+ {
+ id: 'deliveries',
+ name: 'Deliveries',
+ description: 'Delivery records, queues, and rider-delivery joins.',
+ endpoints: [
+ {
+ name: 'getdeliveries',
+ method: 'GET',
+ url: '/api/rest/xpress/deliveries/getdeliveries?tenantid=10&status=Delivered&fromdate=2026-05-01T00:00:00&todate=2026-05-05T23:59:59&keyword=%john%&limit=10&offset=0',
+ description: 'List deliveries with filters: tenant, status, date range, keyword.',
+ _note: "Source query 'tenantid=10=Delivered' reconstructed as 'tenantid=10&status=Delivered'."
+ },
+ {
+ name: 'getdeliveryqueues',
+ method: 'GET',
+ url: '/api/rest/xpress/deliveries/getdeliveryqueues?userid=1277&fdate=2025-12-30T00:00:00 &tdate=2025-12-30T23:59:59',
+ description: 'Retrieve delivery queue snapshots for a user/date range.',
+ _note: "Source name 'tdeliveryqueues' truncated β assumed 'Getdeliveryqueues'."
+ },
+ {
+ name: 'deliverysummary',
+ method: 'GET',
+ url: '/api/rest/xpress/deliveries/deliverysummary?appuserid=1&fromdate=2026-03-31T00:00:00&todate=2026-03-31T23:59:59&status=active&applocationid=1&locationid=1160&tenantid=916&userid=865',
+ description: 'Aggregated delivery summary for an app user.'
+ },
+ {
+ name: 'getriderbydelivery',
+ method: 'GET',
+ url: '/api/rest/xpress/deliveries/getriderbydelivery/?applocationid=&tenantid=&locationid=&fromdate=&todate=&keyword=',
+ description: 'Map riders to deliveries within a location and date range.',
+ _note: "Source URL was missing 'https' scheme β restored."
+ }
+ ]
+ },
+ {
+ id: 'orders',
+ name: 'Orders',
+ description: 'Order records, details, and aggregate summaries.',
+ endpoints: [
+ {
+ name: 'getorders',
+ method: 'GET',
+ url: '/api/rest/xpress/orders/tenant/getorders?start=2026-05-01T00:00:00&end=2026-05-31T23:59:59&status=delivered&limit=10&offset=0',
+ description: 'List orders within a time frame and status.'
+ },
+ {
+ name: 'getordersummary',
+ method: 'GET',
+ url: '/api/rest/getordersummary/?tenantid=1079&fromdate=2025-07-24&todate=2025-07-24&configid=9',
+ description: 'High-level summary of orders for a tenant/date range.'
+ },
+ {
+ name: 'getorderdetails',
+ method: 'GET',
+ url: '/api/rest/xpress/orders/getorderdetails?orderheaderid=6562',
+ description: 'Get full details of an order by header ID.'
+ },
+ {
+ name: 'getlocationsummary',
+ method: 'GET',
+ url: '/api/rest/xpress/orders/getlocationsummary?tenantid=916',
+ description: 'Per-location summary of orders.'
+ }
+ ]
+ },
+ {
+ id: 'products',
+ name: 'Products',
+ description: 'Product catalog, categories, and subcategories.',
+ endpoints: [
+ {
+ name: 'getproductcategories',
+ method: 'GET',
+ url: '/api/rest/xpress/products/getproductcategories?moduleid=1',
+ description: 'List root product categories for a module.'
+ },
+ {
+ name: 'getproductsubcategories',
+ method: 'GET',
+ url: '/api/rest/xpress/products/getproductsubcategories?categoryid=1001',
+ description: 'List subcategories under a product category.'
+ }
+ ]
+ },
+ {
+ id: 'invoice',
+ name: 'Invoice',
+ description: 'Invoice insights and billing analytics.',
+ endpoints: [
+ {
+ name: 'getinvoiceinsight',
+ method: 'GET',
+ url: '/api/rest/xpress/invoice/getinvoiceinsight?tenantid=916',
+ description: 'Retrieve invoice insights and statistics for a tenant.',
+ _note: "Source name 'Getinvoinsights' assumed to be 'Getinvoiceinsight'."
+ }
+ ]
+ },
+ {
+ id: 'payments',
+ name: 'Payments',
+ description: 'Payment requests and settlements.',
+ endpoints: [
+ {
+ name: 'getpaymentrequest',
+ method: 'GET',
+ url: '/api/rest/xpress/payments/requests/getpaymentrequest?partnerid=44&status=1',
+ description: 'List payment requests for a partner by status.'
+ }
+ ]
+ }
+];
+
+module.exports = { existingTopics };
diff --git a/package-lock.json b/package-lock.json
index 9bf76ae..96a37fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -67,7 +67,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -1478,7 +1477,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -2337,7 +2335,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -2694,7 +2691,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -2916,7 +2912,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3405,7 +3400,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -3531,7 +3525,6 @@
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -3625,7 +3618,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
diff --git a/parse_apis.cjs b/parse_apis.cjs
new file mode 100644
index 0000000..5a3c67e
--- /dev/null
+++ b/parse_apis.cjs
@@ -0,0 +1,182 @@
+const fs = require('fs');
+const path = require('path');
+const { existingTopics } = require('./original_topics.cjs');
+
+const getApisContent = fs.readFileSync(path.join(__dirname, "Get Api's.txt"), 'utf8');
+const postApisContent = fs.readFileSync(path.join(__dirname, "Post, put and delete Api's.txt"), 'utf8');
+
+const endpoints = [];
+
+// Parse GET APIs
+let currentGet = null;
+const getLines = getApisContent.split('\n');
+for (let line of getLines) {
+ const trimmed = line.trim();
+ if (trimmed.startsWith('# ')) {
+ currentGet = { method: 'GET', description: trimmed.substring(2), name: trimmed.substring(2) };
+ } else if (trimmed.startsWith('https://')) {
+ if (currentGet) {
+ currentGet.url = trimmed;
+ endpoints.push(currentGet);
+ currentGet = null;
+ }
+ }
+}
+
+// Better Parse POST/PUT/DELETE APIs
+const postLines = postApisContent.split('\n');
+let i = 0;
+
+while (i < postLines.length) {
+ let line = postLines[i].trim();
+
+ if (line.startsWith('POST ') || line.startsWith('PUT ') || line.startsWith('DELETE ')) {
+ const parts = line.split(' ');
+ const method = parts[0];
+ const name = parts.slice(1).join(' ');
+ let url = '';
+ let body = null;
+ let description = name;
+
+ i++;
+ while (i < postLines.length) {
+ let l = postLines[i].trim();
+ if (l.startsWith('POST ') || l.startsWith('PUT ') || l.startsWith('DELETE ') || l.startsWith('1. ') || l.startsWith('2. ') || l.startsWith('3. ') || l.startsWith('4. ') || l.startsWith('5. ') || l.startsWith('6. ') || l.startsWith('π»') || l.startsWith('π±')) {
+ break; // Next block starts
+ }
+
+ if (l.startsWith('URL: ')) {
+ url = l.substring(5).trim();
+ } else if (l.startsWith('Payload: (Uses the same schema')) {
+ description += ' ' + l;
+ } else if (l === 'json') {
+ // start capturing json
+ let jsonStr = '';
+ i++;
+ while (i < postLines.length) {
+ let jl = postLines[i].trim();
+ if (jl.startsWith('POST ') || jl.startsWith('PUT ') || jl.startsWith('DELETE ') || jl.startsWith('1. ') || jl.startsWith('2. ') || jl.startsWith('3. ') || jl.startsWith('4. ') || jl.startsWith('5. ') || jl.startsWith('6. ') || jl.startsWith('π»') || jl.startsWith('π±')) {
+ break;
+ }
+ jsonStr += postLines[i] + '\n';
+ i++;
+ }
+ let rawBody = jsonStr.trim();
+ try {
+ body = JSON.parse(rawBody);
+ } catch (e) {
+ body = rawBody; // fallback to string
+ }
+ continue;
+ }
+ i++;
+ }
+
+ if (url) {
+ const ep = { method, name, description, url };
+ if (body) {
+ ep.body = body;
+ }
+ endpoints.push(ep);
+ }
+ } else {
+ i++;
+ }
+}
+
+// Second pass: Resolve "Uses the same schema as"
+endpoints.forEach(ep => {
+ if (!ep.body && ep.description.includes('Uses the same schema as')) {
+ const match = ep.description.match(/Uses the same schema as(.*?)\)/);
+ if (match) {
+ let refText = match[1].trim().toLowerCase();
+ // Try to find the referenced endpoint
+ let refEp = endpoints.find(e => {
+ if (!e.body) return false;
+ let eName = e.name.toLowerCase();
+ // Remove 'web ' or 'mobile ' for looser matching
+ refText = refText.replace('web ', '').replace('mobile ', '');
+ eName = eName.replace('web ', '').replace('mobile ', '');
+ return refText.includes(eName) || eName.includes(refText);
+ });
+ if (refEp) {
+ ep.body = refEp.body;
+ }
+ }
+ }
+});
+
+// Map to topics
+const topicMapping = {
+ 'utils': ['utils', 'config', 'category'],
+ 'users': ['user'],
+ 'partners': ['partner', 'rider'],
+ 'tenants': ['tenant', 'location'],
+ 'customers': ['customer'],
+ 'deliveries': ['deliver'],
+ 'orders': ['order'],
+ 'products': ['product', 'catalog', 'stock'],
+ 'invoice': ['invoice'],
+ 'payments': ['payment']
+};
+
+const mapToTopic = (url) => {
+ const urlLower = url.toLowerCase();
+ for (const [topicId, keywords] of Object.entries(topicMapping)) {
+ if (urlLower.includes('/' + topicId + '/')) return topicId;
+ for (const kw of keywords) {
+ if (urlLower.includes('/' + kw)) return topicId;
+ }
+ }
+ return 'utils';
+};
+
+const restTopicsMap = {
+ utils: { id: 'utils', name: 'Utils', description: 'Shared lookup endpoints roles, locations, configs, app types, pricing.', endpoints: [] },
+ users: { id: 'users', name: 'Users', description: 'Manage users and roles across the Xpress platform.', endpoints: [] },
+ partners: { id: 'partners', name: 'Partners', description: 'Partners, riders, locations, shifts, and rider pricing.', endpoints: [] },
+ tenants: { id: 'tenants', name: 'Tenants', description: 'Tenant accounts info, locations, customers, orders, pricing, summary.', endpoints: [] },
+ customers: { id: 'customers', name: 'Customers', description: 'Customer accounts, lookups, and search.', endpoints: [] },
+ deliveries: { id: 'deliveries', name: 'Deliveries', description: 'Delivery records, queues, and rider-delivery joins.', endpoints: [] },
+ orders: { id: 'orders', name: 'Orders', description: 'Order records, details, and aggregate summaries.', endpoints: [] },
+ products: { id: 'products', name: 'Products', description: 'Product catalog, categories, and subcategories.', endpoints: [] },
+ invoice: { id: 'invoice', name: 'Invoice', description: 'Invoice insights and billing analytics.', endpoints: [] },
+ payments: { id: 'payments', name: 'Payments', description: 'Payment requests and settlements.', endpoints: [] }
+};
+
+endpoints.forEach(ep => {
+ let parsedUrl;
+ try {
+ parsedUrl = new URL(ep.url);
+ } catch (e) {
+ return;
+ }
+ const fullPath = parsedUrl.pathname + parsedUrl.search;
+ const topicId = mapToTopic(fullPath);
+
+ if (restTopicsMap[topicId]) {
+ const newEp = {
+ name: ep.name,
+ method: ep.method,
+ url: fullPath,
+ description: ep.description
+ };
+ if (ep.body) {
+ newEp.body = ep.body;
+ }
+ restTopicsMap[topicId].endpoints.push(newEp);
+ }
+});
+
+const restTopics = Object.values(restTopicsMap);
+
+const out = `export const LEGACY_BASE_URL = 'https://api.workolik.com';
+export const REST_BASE_URL = 'https://fiesta.nearle.app';
+
+export const legacyTopics = ${JSON.stringify(existingTopics, null, 2)};
+
+export const restTopics = ${JSON.stringify(restTopics, null, 2)};
+`;
+
+fs.writeFileSync(path.join(__dirname, 'src', 'data', 'topics.js'), out, 'utf8');
+console.log('Successfully updated topics.js with ' + endpoints.length + ' parsed endpoints (fixed payload parsing).');
diff --git a/server.js b/server.js
index e04973f..0312892 100644
--- a/server.js
+++ b/server.js
@@ -19,7 +19,8 @@ import 'dotenv/config'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const PORT = Number(process.env.PORT) || 3000
const SECRET = (process.env.HASURA_ADMIN_SECRET || '').trim()
-const TARGET = 'https://api.workolik.com'
+const TARGET_LEGACY = 'https://api.workolik.com'
+const TARGET_REST = 'https://fiesta.nearle.app'
if (!SECRET) {
console.warn('[xpress-docs] WARNING: HASURA_ADMIN_SECRET is not set. Proxied API calls will be sent without auth.')
@@ -27,13 +28,9 @@ if (!SECRET) {
const app = express()
-app.use('/api', createProxyMiddleware({
- target: TARGET,
+const commonProxyOptions = {
changeOrigin: true,
secure: true,
- // The mount strips '/api' from req.url; add it back so the target URL
- // stays /api/rest/...
- pathRewrite: (p) => '/api' + p,
on: {
proxyReq: (proxyReq) => {
if (SECRET) proxyReq.setHeader('x-hasura-admin-secret', SECRET)
@@ -46,6 +43,18 @@ app.use('/api', createProxyMiddleware({
res.end(JSON.stringify({ error: 'proxy_error', message: err.message }))
}
}
+}
+
+app.use('/api', createProxyMiddleware({
+ ...commonProxyOptions,
+ target: TARGET_LEGACY,
+ pathRewrite: (p) => '/api' + p
+}))
+
+app.use('/live', createProxyMiddleware({
+ ...commonProxyOptions,
+ target: TARGET_REST,
+ pathRewrite: (p) => '/live' + p
}))
// Built React app
@@ -57,6 +66,7 @@ app.get('*', (_req, res) => {
app.listen(PORT, () => {
console.log(`[xpress-docs] listening on http://localhost:${PORT}`)
- console.log(`[xpress-docs] proxying /api/* -> ${TARGET}/api/*`)
+ console.log(`[xpress-docs] proxying /api/* -> ${TARGET_LEGACY}/api/*`)
+ console.log(`[xpress-docs] proxying /live/* -> ${TARGET_REST}/live/*`)
console.log(`[xpress-docs] admin secret: ${SECRET ? 'loaded' : 'NOT SET'}`)
})
diff --git a/src/App.jsx b/src/App.jsx
index 2913cb8..e2dbf5a 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -2,39 +2,67 @@ import { useMemo, useState } from 'react'
import Sidebar from './components/Sidebar'
import Introduction from './components/Introduction'
import TopicView from './components/TopicView'
-import { topics } from './data/topics'
+import { legacyTopics, restTopics, LEGACY_BASE_URL, REST_BASE_URL } from './data/topics'
+
+const processTopics = (topics, baseUrl, type) =>
+ topics.map(t => ({ ...t, baseUrl, type, uniqueId: `${type}-${t.id}` }))
+
+const allLegacy = processTopics(legacyTopics, LEGACY_BASE_URL, 'legacy')
+const allRest = processTopics(restTopics, REST_BASE_URL, 'rest')
+
+function filterTopics(topics, q) {
+ if (!q) return topics
+ return topics.filter((t) =>
+ t.name.toLowerCase().includes(q) ||
+ (t.description || '').toLowerCase().includes(q) ||
+ t.endpoints.some((e) =>
+ e.name.toLowerCase().includes(q) ||
+ e.url.toLowerCase().includes(q) ||
+ (e.description || '').toLowerCase().includes(q)
+ )
+ )
+}
export default function App() {
const [activeTopic, setActiveTopic] = useState(null)
const [searchQuery, setSearchQuery] = useState('')
- const visibleTopics = useMemo(() => {
- const q = searchQuery.trim().toLowerCase()
- if (!q) return topics
- return topics.filter((t) =>
- t.name.toLowerCase().includes(q) ||
- (t.description || '').toLowerCase().includes(q) ||
- t.endpoints.some((e) =>
- e.name.toLowerCase().includes(q) ||
- e.url.toLowerCase().includes(q) ||
- (e.description || '').toLowerCase().includes(q)
- )
- )
- }, [searchQuery])
+ const visibleLegacy = useMemo(() => filterTopics(allLegacy, searchQuery.trim().toLowerCase()), [searchQuery])
+ const visibleRest = useMemo(() => filterTopics(allRest, searchQuery.trim().toLowerCase()), [searchQuery])
return (
-
+
+
+ {/* Ambient background glows */}
+
+
+
+
-
+
+
{activeTopic
- ?
- : }
+ ? (
+
+
+
+ )
+ : (
+
+
+
+ )}
)
diff --git a/src/components/EndpointCard.jsx b/src/components/EndpointCard.jsx
index db67e06..779b270 100644
--- a/src/components/EndpointCard.jsx
+++ b/src/components/EndpointCard.jsx
@@ -1,8 +1,7 @@
import { useMemo, useState } from 'react'
import {
- Play, Copy, Check, CheckCircle2, XCircle, Loader2
+ Play, Copy, Check, CheckCircle2, AlertCircle, Server, FileJson, Loader2
} from 'lucide-react'
-import { BASE_URL } from '../data/topics'
import { highlightJSON } from '../lib/highlight'
function safeDecode(v) {
@@ -21,32 +20,25 @@ function parseUrl(url) {
return { path, params }
}
-export default function EndpointCard({
- endpoint,
- onSend,
- result,
- loading
-}) {
+export default function EndpointCard({ endpoint, baseUrl, onSend, result, loading }) {
const { path, params: parsedParams } = useMemo(() => parseUrl(endpoint.url), [endpoint.url])
const paramDefs = endpoint.params || parsedParams
const [values, setValues] = useState(() =>
Object.fromEntries(paramDefs.map((p) => [p.name, p.default ?? '']))
)
- const [copied, setCopied] = useState(false)
+ const [copied, setCopied] = useState(false)
const [urlCopied, setUrlCopied] = useState(false)
const composedUrl = useMemo(() => {
- if (paramDefs.length === 0) return BASE_URL + path
+ if (paramDefs.length === 0) return baseUrl + path
const qs = paramDefs
.map((p) => `${p.name}=${encodeURIComponent(values[p.name] ?? '')}`)
.join('&')
- return `${BASE_URL}${path}?${qs}`
- }, [path, paramDefs, values])
+ return `${baseUrl}${path}?${qs}`
+ }, [path, paramDefs, values, baseUrl])
- const handleSend = () => {
- onSend(endpoint, composedUrl)
- }
+ const handleSend = () => onSend(endpoint, composedUrl)
const copyResponse = async () => {
if (!result || result.kind !== 'response') return
@@ -66,160 +58,205 @@ export default function EndpointCard({
} catch {}
}
- return (
-
- {/* Header */}
-
-
-
- {endpoint.method}
-
-
{endpoint.name}
-
- {endpoint.description && (
-
{endpoint.description}
- )}
-
+ const methodColor = {
+ GET: 'bg-emerald-100/50 text-emerald-700 border-emerald-200',
+ POST: 'bg-blue-100/50 text-blue-700 border-blue-200',
+ PUT: 'bg-amber-100/50 text-amber-700 border-amber-200',
+ DELETE: 'bg-red-100/50 text-red-700 border-red-200',
+ PATCH: 'bg-purple-100/50 text-purple-700 border-purple-200',
+ }[endpoint.method] || 'bg-slate-100/50 text-slate-700 border-slate-200'
- {/* Live URL bar */}
-
-
-
Request URL
-
- {urlCopied ? : }
- {urlCopied ? 'Copied' : 'Copy'}
-
-
-
-
{endpoint.method} {' '}
-
{BASE_URL}
-
{path}
+ const isOk = result?.kind === 'response' && result.ok
+ const isErr = result?.kind === 'response' && !result.ok
+ const isNet = result?.kind === 'network-error'
+
+ return (
+
+
+
+ {/* Left Column: Description & Parameters */}
+
+
+
+
+ {endpoint.method}
+
+
+ {endpoint.name}
+
+
+ {endpoint.description && (
+
{endpoint.description}
+ )}
+
+
+ {/* URL bar */}
+
+
+ {baseUrl}
+ {path}
+
+
+ {/* Query Parameters */}
{paramDefs.length > 0 && (
- <>
-
?
- {paramDefs.map((p, i) => (
-
- {p.name}
- =
- {encodeURIComponent(values[p.name] ?? '')}
- {i < paramDefs.length - 1 && & }
-
- ))}
- >
- )}
-
-
-
- {/* Query Parameters */}
- {paramDefs.length > 0 && (
-
-
- Query Parameters
-
-
- {paramDefs.map((p) => (
-
-
- {p.name}
-
- {p.type || 'STRING'}
-
-
-
setValues((v) => ({ ...v, [p.name]: e.target.value }))}
- className="mono text-sm px-3 py-1.5 w-full bg-white border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500/20 focus:border-brand-500 transition-all"
- placeholder={p.default || ''}
- />
+
+
+ Query Parameters
+
+
+ {paramDefs.map((p) => (
+
+
+ {p.name}
+ {p.type || 'string'}
+
+
+ setValues((v) => ({ ...v, [p.name]: e.target.value }))
+ }
+ />
+
+ ))}
- ))}
-
-
- )}
-
- {/* Send button */}
-
-
- {loading
- ?
- : }
- {loading ? 'Sending...' : 'Send Request'}
-
-
-
- {/* Response panel β only visible on the active endpoint */}
- {result &&
}
-
- )
-}
-
-function ResponsePanel({ result, onCopy, copied }) {
- const isOk = result.kind === 'response' && result.ok
- const isError = result.kind === 'response' && !result.ok
- const isNet = result.kind === 'network-error'
- const status = result.status
- const headerBg = '#161922'
- const bodyBg = '#0f1117'
-
- const StatusBadge = () => {
- if (isOk) return (
-
- {status}
-
- )
- if (isError) return (
-
- {status}
-
- )
- if (isNet) return (
-
- NETWORK
-
- )
- return null
- }
-
- const body = isNet
- ? { error: 'Network error', message: result.message }
- : result.body
-
- const isString = typeof body === 'string'
-
- return (
-
-
-
-
- Response
- {result.ms != null && (
- {result.ms} ms
+
)}
-
- {copied ? : }
- {copied ? 'Copied' : 'Copy'}
-
+
+ {/* Right Column: Execution & Response */}
+
+
+
+ {/* macOS window dots */}
+
+
+
+
+
Request Payload
+
+
+ {/* Payload body or composed URL preview */}
+ {endpoint.body ? (
+
+
+ Request Body (JSON)
+
+
+
+
+
+ ) : (
+
+
+ Full Request URL
+
+
+ {endpoint.method}
+ {baseUrl}
+ {path}
+ {paramDefs.length > 0 && (
+ <>
+ ?
+ {paramDefs.map((p, i) => (
+
+ {p.name}
+ =
+ {encodeURIComponent(values[p.name] ?? '')}
+ {i < paramDefs.length - 1 && & }
+
+ ))}
+ >
+ )}
+
+
+ )}
+
+ {/* Execution Bar */}
+
+
+
+ Ready
+
+
+ {urlCopied ? : }
+ {urlCopied ? 'Copied' : 'Copy URL'}
+
+
+
+ {loading
+ ?
+ : }
+ {loading ? 'Executing...' : 'Send Request'}
+
+
+
+ {/* Response */}
+ {result && (
+
+
+
Response JSON
+
+ {result.ms != null && (
+
{result.ms} ms
+ )}
+ {isOk && (
+
+ {result.status}
+
+ )}
+ {isErr && (
+
+ {result.status}
+
+ )}
+ {isNet && (
+
+ NETWORK ERR
+
+ )}
+ {result.kind === 'response' && (
+
+ {copied ? : }
+ {copied ? 'Copied' : 'Copy'}
+
+ )}
+
+
+ {isNet ? (
+
+ Network error: {result.message}
+
+ ) : typeof result.body === 'string' ? (
+
{result.body}
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
-
- {isString
- ? {body}
- : }
-
)
}
diff --git a/src/components/Introduction.jsx b/src/components/Introduction.jsx
index 2b20bc5..d64e7c1 100644
--- a/src/components/Introduction.jsx
+++ b/src/components/Introduction.jsx
@@ -1,60 +1,99 @@
-import { Zap, BookOpen, Code2, Server } from 'lucide-react'
-import { BASE_URL, topics } from '../data/topics'
+import { ArrowRight, Code2, Zap, Truck, Users, Building2, Package, ShoppingCart, CreditCard, FileText, UserCircle, Bike, Wrench } from 'lucide-react'
+import { LEGACY_BASE_URL, REST_BASE_URL } from '../data/topics'
-export default function Introduction() {
- const endpointCount = topics.reduce((n, t) => n + t.endpoints.length, 0)
+const topicIcons = {
+ utils: Wrench,
+ users: Users,
+ partners: Bike,
+ tenants: Building2,
+ customers: UserCircle,
+ deliveries: Truck,
+ orders: ShoppingCart,
+ products: Package,
+ invoice: FileText,
+ payments: CreditCard,
+}
+
+export default function Introduction({ allLegacy, allRest, setActiveTopic }) {
+ const allTopics = [...allLegacy, ...allRest]
return (
-
-
-
-
-
-
-
EXpress Developer Docs
-
Reference for the EXpress REST API.
+
+
+
+
+ v1.0 Developer API
+
+
+
+ Nearle Express API
+
+
+
+ A comprehensive platform for managing tenants, users, partners, customers,
+ orders, deliveries, products, invoices, and payments across the Express network.
+
+
+
+
Base URLs
+
+
+
Hasura API
+
{LEGACY_BASE_URL}
+
+
+
REST API
+
{REST_BASE_URL}
+
-
- Welcome to the EXpress API documentation. The EXpress API exposes the operations
- you need to manage tenants, users, partners, customers, orders, deliveries,
- products, invoices, and payments across the platform.
-
+
+ {allTopics.map((topic, index) => {
+ const Icon = topicIcons[topic.id] || Zap
+ return (
+
setActiveTopic(topic)}
+ className="group relative bg-white p-6 rounded-2xl border border-slate-200/60 shadow-sm hover:shadow-xl hover:shadow-brand-500/5 hover:border-brand-200 transition-all duration-300 cursor-pointer overflow-hidden"
+ style={{ animationDelay: `${index * 100}ms` }}
+ >
+
-
-
-
- Topics
-
-
{topics.length}
-
-
-
- Endpoints
-
-
{endpointCount}
-
-
+
+
+
+
+
+ {topic.name}
+
+
+
+
+ {topic.type === 'legacy' ? 'Hasura' : 'REST'}
+
+ {topic.endpoints.length} endpoint{topic.endpoints.length !== 1 ? 's' : ''}
+
+
+ {topic.description}
+
+
+
+ )
+ })}
-
Base URL
-
- {BASE_URL}
-
+
+
+
+ Explore interactive endpoints for both Hasura and REST APIs. All requests route through the dev proxy which injects authentication headers securely.
+
+
-
Reference layout
-
- Endpoints are grouped in the sidebar by their URL segment after
- {' '}/api/rest/xpress/.
- Pick a topic on the left to see all endpoints under it, each with its method,
- full URL, and a short description.
-
)
}
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 357c162..150fe0c 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -1,19 +1,58 @@
import { useState } from 'react'
-import { Search, Zap, ChevronDown, ChevronRight, Terminal } from 'lucide-react'
+import { Search, ChevronRight, ChevronDown, Layers, Terminal } from 'lucide-react'
import { getTopicIcon } from '../lib/icons'
-export default function Sidebar({ topics, activeTopic, setActiveTopic, searchQuery, setSearchQuery }) {
- const [open, setOpen] = useState({ general: true, topics: true })
+export default function Sidebar({ legacyTopics, restTopics, activeTopic, setActiveTopic, searchQuery, setSearchQuery }) {
+ const [open, setOpen] = useState({ general: true, legacy: true, rest: true })
const toggle = (k) => setOpen((s) => ({ ...s, [k]: !s[k] }))
+ const renderTopicGroup = (topics, title, key) => (
+
+
toggle(key)}
+ className="w-full flex items-center justify-between px-2 py-1.5 text-slate-800 font-semibold text-sm hover:text-brand-600 transition-colors group"
+ >
+
+ {title}
+
+ {open[key] ? : }
+
+
+ {topics.map((t) => {
+ const isActive = activeTopic?.uniqueId === t.uniqueId
+ const Icon = getTopicIcon(t.id)
+ return (
+ setActiveTopic(t)}
+ className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-all duration-200 group ${
+ isActive
+ ? 'text-brand-700 bg-brand-50 shadow-sm font-medium'
+ : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100/50'
+ }`}
+ >
+
+ {t.name}
+
+ )
+ })}
+
+
+ )
+
return (
+
+ {/* Brand Header */}
-
+
-
EXpress
+
Nearle Express
@@ -29,67 +68,34 @@ export default function Sidebar({ topics, activeTopic, setActiveTopic, searchQue
+
+ {/* Getting Started */}
toggle('general')}
className="w-full flex items-center justify-between px-2 py-1.5 text-slate-800 font-semibold text-sm hover:text-brand-600 transition-colors group"
>
-
- Getting Started
-
- {open.general
- ?
- : }
+ Getting Started
+ {open.general ? : }
setActiveTopic(null)}
- className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-all duration-200 ${activeTopic
- ? 'text-slate-600 hover:text-slate-900 hover:bg-slate-100/50'
- : 'text-brand-700 bg-brand-50 shadow-sm font-medium'
- }`}
+ className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-all duration-200 ${
+ activeTopic === null
+ ? 'text-brand-700 bg-brand-50 shadow-sm font-medium'
+ : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100/50'
+ }`}
>
-
+
Introduction
-
-
toggle('topics')}
- className="w-full flex items-center justify-between px-2 py-1.5 text-slate-800 font-semibold text-sm hover:text-brand-600 transition-colors group"
- >
-
- API Reference
-
- {open.topics
- ?
- : }
-
-
- {topics.map((t) => {
- const isActive = activeTopic?.id === t.id
- const Icon = getTopicIcon(t.id)
- return (
- setActiveTopic(t)}
- className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-all duration-200 group ${isActive
- ? 'text-brand-700 bg-brand-50 shadow-sm font-medium'
- : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100/50'
- }`}
- >
-
- {t.name}
-
- )
- })}
-
-
+ {legacyTopics.length > 0 && renderTopicGroup(legacyTopics, 'Hasura API', 'legacy')}
+ {restTopics.length > 0 && renderTopicGroup(restTopics, 'REST API', 'rest')}
+
diff --git a/src/components/TopicView.jsx b/src/components/TopicView.jsx
index bebe022..f838ab8 100644
--- a/src/components/TopicView.jsx
+++ b/src/components/TopicView.jsx
@@ -1,12 +1,15 @@
import { useEffect, useRef, useState } from 'react'
import EndpointCard from './EndpointCard'
import { getTopicIcon } from '../lib/icons'
-import { BASE_URL } from '../data/topics'
-// Strip the documented BASE_URL so fetches are same-origin and get
-// proxied by Vite (dev) or Express (prod), which injects the secret header.
-function toProxyPath(fullUrl) {
- if (fullUrl.startsWith(BASE_URL)) return fullUrl.slice(BASE_URL.length)
+import { LEGACY_BASE_URL } from '../data/topics'
+
+function toProxyPath(fullUrl, baseUrl) {
+ // Only proxy the legacy Hasura API (to inject the admin secret via server)
+ if (baseUrl === LEGACY_BASE_URL && fullUrl.startsWith(baseUrl)) {
+ return fullUrl.slice(baseUrl.length)
+ }
+ // Let the browser hit the new REST API directly (it supports CORS)
return fullUrl
}
@@ -21,8 +24,6 @@ export default function TopicView({ topic, searchQuery }) {
)
: topic.endpoints
- // Only one endpoint's response is visible at a time. Sending on a new
- // endpoint replaces the previous one (and cancels its in-flight fetch).
const [active, setActive] = useState(null)
const abortRef = useRef(null)
@@ -41,12 +42,28 @@ export default function TopicView({ topic, searchQuery }) {
setActive({ name: endpoint.name, result: null, loading: true })
const start = Date.now()
+
try {
- const res = await fetch(toProxyPath(composedUrl), {
+ const fetchOptions = {
method: endpoint.method,
headers: { 'Content-Type': 'application/json' },
signal: controller.signal
- })
+ }
+
+ if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && endpoint.body) {
+ if (typeof endpoint.body === 'string') {
+ try {
+ // Re-stringify in case it's a badly formatted JSON string, but mostly it's just raw string
+ fetchOptions.body = JSON.stringify(JSON.parse(endpoint.body))
+ } catch {
+ fetchOptions.body = endpoint.body
+ }
+ } else {
+ fetchOptions.body = JSON.stringify(endpoint.body)
+ }
+ }
+
+ const res = await fetch(toProxyPath(composedUrl, topic.baseUrl), fetchOptions)
const ms = Date.now() - start
const text = await res.text()
let body
@@ -71,23 +88,26 @@ export default function TopicView({ topic, searchQuery }) {
}
return (
-
-
-
-
-
-
-
{topic.name}
-
{topic.description}
-
- {filtered.length} endpoint{filtered.length === 1 ? '' : 's'}
-
+
+
+
{topic.name}
+
{topic.description}
+
+
+
+ {topic.type === 'legacy' ? 'Hasura API' : 'REST API'}
+
+ {filtered.length} endpoint{filtered.length !== 1 ? 's' : ''}
{filtered.length === 0 ? (
-
+
No endpoints match "{searchQuery}".
) : (
@@ -95,8 +115,9 @@ export default function TopicView({ topic, searchQuery }) {
const isActive = active?.name === e.name
return (
{
if (secret) proxyReq.setHeader('x-hasura-admin-secret', secret)
})
}
+ },
+ '/live': {
+ target: 'https://fiesta.nearle.app',
+ changeOrigin: true,
+ secure: true,
+ configure: (proxy) => {
+ proxy.on('proxyReq', (proxyReq) => {
+ if (secret) proxyReq.setHeader('x-hasura-admin-secret', secret)
+ })
+ }
}
}
}