diff --git a/Nearle_Full_API_Documentation.docx b/Nearle_Full_API_Documentation.docx new file mode 100644 index 0000000..b1feea8 Binary files /dev/null and b/Nearle_Full_API_Documentation.docx differ diff --git a/scratch/docx_content.txt b/scratch/docx_content.txt new file mode 100644 index 0000000..b6093c4 Binary files /dev/null and b/scratch/docx_content.txt differ diff --git a/scratch/docx_content_utf8.txt b/scratch/docx_content_utf8.txt new file mode 100644 index 0000000..a543282 --- /dev/null +++ b/scratch/docx_content_utf8.txt @@ -0,0 +1,233 @@ +0: Nearle Mobile API – Full GraphQL Documentation +1: GetCustomerLocations +2: REST Params: +3: {"customerid": 6060} +4: GraphQL Query: +5: query GetCustomerLocations($customerid: bigint!) { +6: customer_locations(where: { customerid: { _eq: $customerid } }) { +7: locationid +8: latitude +9: longitude +10: address +11: } +12: } +13: GraphQL Variables: +14: {"customerid": 6060} +15: GetCustomerOrdersV3 +16: REST Params: +17: {"customerid":6060,"tenantid":1087,"moduleid":2,"fromdate":"2026-01-01T00:00:00","todate":"2026-12-31T23:59:59","orderstatus":"delivered","keyword":"%pizza%","limit":10,"offset":0} +18: GraphQL Query: +19: query GetCustomerOrders($customerid: bigint!, $tenantid: bigint!, $moduleid: bigint!, $fromdate: timestamptz!, $todate: timestamptz!, $orderstatus: String!, $keyword: String, $limit: Int!, $offset: Int!) { +20: orders( +21: where: { +22: customerid: { _eq: $customerid } +23: tenantid: { _eq: $tenantid } +24: moduleid: { _eq: $moduleid } +25: orderstatus: { _eq: $orderstatus } +26: orderdate: { _gte: $fromdate, _lte: $todate } +27: _or: [{ orderid: { _ilike: $keyword } }] +28: } +29: limit: $limit +30: offset: $offset +31: ) { +32: orderid +33: orderstatus +34: orderamount +35: } +36: } +37: GraphQL Variables: +38: {"customerid":6060,"tenantid":1087,"moduleid":2,"fromdate":"2026-01-01T00:00:00","todate":"2026-12-31T23:59:59","orderstatus":"delivered","keyword":"%pizza%","limit":10,"offset":0} +39: GetCustomer +40: REST Params: +41: {"customerid":6060} +42: GraphQL Query: +43: query GetCustomer($customerid: bigint!) { +44: customers(where: { customerid: { _eq: $customerid } }) { +45: customerid +46: name +47: contactno +48: } +49: } +50: GraphQL Variables: +51: {"customerid":6060} +52: GetCustomerRequests +53: REST Params: +54: {"customerid":6060,"limit":10,"offset":0} +55: GraphQL Query: +56: query GetCustomerRequests($customerid: bigint!, $limit: Int!, $offset: Int!) { +57: customer_requests(where: { customerid: { _eq: $customerid } }, limit: $limit, offset: $offset) { +58: requestid +59: status +60: } +61: } +62: GraphQL Variables: +63: {"customerid":6060,"limit":10,"offset":0} +64: GetProductSubcategories +65: REST Params: +66: {"categoryid":1001} +67: GraphQL Query: +68: query GetProductSubcategories($categoryid: bigint!) { +69: app_subcategory(where: { categoryid: { _eq: $categoryid } }) { +70: subcategoryid +71: subcategoryname +72: } +73: } +74: GraphQL Variables: +75: {"categoryid":1001} +76: GetAppCategories +77: REST Params: +78: {} +79: GraphQL Query: +80: query GetAppCategories { +81: app_category { +82: categoryid +83: categoryname +84: } +85: } +86: GraphQL Variables: +87: {} +88: GetProductVariants +89: REST Params: +90: {"tenantid":1087,"subcategoryid":14} +91: GraphQL Query: +92: query GetProductVariants($tenantid: bigint!, $subcategoryid: bigint!) { +93: product_variants(where: { tenantid: { _eq: $tenantid }, subcategoryid: { _eq: $subcategoryid } }) { +94: variantid +95: productname +96: price +97: } +98: } +99: GraphQL Variables: +100: {"tenantid":1087,"subcategoryid":14} +101: GetTenantPromotions +102: REST Params: +103: {"tenantid":1087,"locationid":1} +104: GraphQL Query: +105: query GetTenantPromotions($tenantid: bigint!, $locationid: bigint!) { +106: promotions(where: { tenantid: { _eq: $tenantid }, locationid: { _eq: $locationid } }) { +107: promotionid +108: title +109: } +110: } +111: GraphQL Variables: +112: {"tenantid":1087,"locationid":1} +113: GetAppConfig +114: REST Params: +115: {"configid":15} +116: GraphQL Query: +117: query GetAppConfig($configid: bigint!) { +118: app_config(where: { configid: { _eq: $configid } }) { +119: configkey +120: configvalue +121: } +122: } +123: GraphQL Variables: +124: {"configid":15} +125: searchcustomers +126: REST Params: +127: {"tenantid":1087,"keyword":"%john%"} +128: GraphQL Query: +129: query SearchCustomers($tenantid: bigint!, $keyword: String!) { +130: customers(where: { tenantid: { _eq: $tenantid }, name: { _ilike: $keyword } }) { +131: customerid +132: name +133: } +134: } +135: GraphQL Variables: +136: {"tenantid":1087,"keyword":"%john%"} +137: getTenantorders +138: REST Params: +139: {} +140: GraphQL Query: +141: query GetTenantOrders { +142: orders { +143: orderid +144: tenantid +145: } +146: } +147: GraphQL Variables: +148: {} +149: getstaff +150: REST Params: +151: {"tenantid":1087} +152: GraphQL Query: +153: query GetStaff($tenantid: bigint!) { +154: staff(where: { tenantid: { _eq: $tenantid } }) { +155: staffid +156: name +157: } +158: } +159: GraphQL Variables: +160: {"tenantid":1087} +161: GetTenantInfo +162: REST Params: +163: {"tenantid":1079} +164: GraphQL Query: +165: query GetTenantInfo($tenantid: bigint!) { +166: tenants(where: { tenantid: { _eq: $tenantid } }) { +167: tenantname +168: city +169: } +170: } +171: GraphQL Variables: +172: {"tenantid":1079} +173: getTenantlocations +174: REST Params: +175: {"tenantid":1} +176: GraphQL Query: +177: query GetTenantLocations($tenantid: bigint!) { +178: tenant_locations(where: { tenantid: { _eq: $tenantid } }) { +179: locationid +180: locationname +181: } +182: } +183: GraphQL Variables: +184: {"tenantid":1} +185: getapplocations +186: REST Params: +187: {} +188: GraphQL Query: +189: query GetAppLocations { +190: app_location { +191: applocationid +192: locationname +193: } +194: } +195: GraphQL Variables: +196: {} +197: getsubcategory +198: REST Params: +199: {"moduleid":6,"categoryid":1} +200: GraphQL Query: +201: query GetSubCategory($moduleid: bigint!, $categoryid: bigint!) { +202: app_subcategory(where: { categoryid: { _eq: $categoryid }, category: { modules: { moduleid: { _eq: $moduleid } } } }) { +203: subcategoryid +204: subcategoryname +205: } +206: } +207: GraphQL Variables: +208: {"moduleid":6,"categoryid":1} +209: GetPartners +210: REST Params: +211: {"applocationid":1,"partnerid":44,"limit":10,"offset":0} +212: GraphQL Query: +213: query GetPartners($applocationid: bigint!, $partnerid: bigint, $limit: Int!, $offset: Int!) { +214: partners(where: { applocationid: { _eq: $applocationid }, partnerid: { _eq: $partnerid } }, limit: $limit, offset: $offset) { +215: partnerid +216: name +217: } +218: } +219: GraphQL Variables: +220: {"applocationid":1,"partnerid":44,"limit":10,"offset":0} +221: GetlocationsConfig +222: REST Params: +223: {} +224: GraphQL Query: +225: query GetLocationConfig { +226: app_locationconfigs { +227: applocationid +228: configkey +229: } +230: } +231: GraphQL Variables: +232: {} diff --git a/scratch/extract_docx.py b/scratch/extract_docx.py new file mode 100644 index 0000000..32a9efb --- /dev/null +++ b/scratch/extract_docx.py @@ -0,0 +1,15 @@ +import docx +import json + +def extract_docx_content(file_path): + doc = docx.Document(file_path) + full_text = [] + for para in doc.paragraphs: + full_text.append(para.text) + return "\n".join(full_text) + +content = extract_docx_content('Nearle_Full_API_Documentation.docx') +with open('scratch/docx_content_utf8.txt', 'w', encoding='utf-8') as f: + lines = content.split('\n') + for i, line in enumerate(lines): + f.write(f"{i}: {line}\n") diff --git a/src/App.jsx b/src/App.jsx index 2881c1b..b98bf6c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,116 +1,66 @@ import React, { useState, useMemo } from 'react'; import Sidebar from './components/Sidebar'; import EndpointViewer from './components/EndpointViewer'; -import { topics } from './data/apiData'; -import { ArrowRight, Code2, Zap, Users, ShoppingCart, Home, Box, Link2 } from 'lucide-react'; +import Introduction from './components/Introduction'; +import { graphqlTopics, GRAPHQL_BASE_URL } from './data/apiData'; +import { restTopics, REST_BASE_URL } from './data/restTopics'; -const topicIcons = { - users: Users, - orders: ShoppingCart, - tenants: Home, - products: Box, - locations: Link2, - partners: Users, -}; +const processTopics = (topics, baseUrl, type) => + topics.map(t => ({ ...t, baseUrl, type, uniqueId: `${type}-${t.id}` })); + +const allGraphql = processTopics(graphqlTopics, GRAPHQL_BASE_URL, 'graphql'); +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) + ) + ); +} function App() { const [activeTopic, setActiveTopic] = useState(null); const [searchQuery, setSearchQuery] = useState(''); - const filteredTopics = useMemo(() => { - if (!searchQuery.trim()) return topics; - - return topics.map(topic => { - const matchTopic = topic.name.toLowerCase().includes(searchQuery.toLowerCase()); - const filteredEndpoints = topic.endpoints.filter(ep => - ep.name.toLowerCase().includes(searchQuery.toLowerCase()) || - ep.description.toLowerCase().includes(searchQuery.toLowerCase()) - ); - - if (matchTopic || filteredEndpoints.length > 0) { - return { - ...topic, - endpoints: filteredEndpoints.length > 0 ? filteredEndpoints : topic.endpoints - }; - } - return null; - }).filter(Boolean); - }, [searchQuery]); + const q = searchQuery.trim().toLowerCase(); + const visibleGraphql = useMemo(() => filterTopics(allGraphql, q), [q]); + const visibleRest = useMemo(() => filterTopics(allRest, q), [q]); return (
- + {/* Ambient background glows */}
- {/* Sidebar */} - - {/* Main Content Area */}
{activeTopic ? (
) : ( -
- -
- - v2.0 Developer API -
- -

- NearleDaily API -

- -

- A comprehensive, lightning-fast platform designed for logistics, order management, and multi-tenant POS administration. -

- -
- {topics.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` }} - > -
- -
-

-
- -
- {topic.name} - -

-

- {topic.description} -

-
-
- )})} -
- -
- -

- Explore real-time interactive endpoints. All requests are authenticated with secure admin secrets internally for demonstration purposes. -

-
- +
+
)}
diff --git a/src/components/EndpointViewer.jsx b/src/components/EndpointViewer.jsx index de90107..b0b2706 100644 --- a/src/components/EndpointViewer.jsx +++ b/src/components/EndpointViewer.jsx @@ -1,86 +1,120 @@ import React, { useState, useEffect } from 'react'; -import { Play, FileJson, Server, CheckCircle2, AlertCircle } from 'lucide-react'; +import { Play, FileJson, Server, CheckCircle2, AlertCircle, Copy, Check, Loader2 } from 'lucide-react'; import { graphqlMeta } from '../data/apiData'; +import { highlightJSON } from '../lib/highlight'; + +function safeDecode(v) { + try { return decodeURIComponent(v); } catch { return v; } +} + +function parseUrlParams(url) { + const [, query = ''] = url.split('?'); + const params = {}; + if (query) { + for (const pair of query.split('&')) { + const [name, value = ''] = pair.split('='); + if (name) params[name] = safeDecode(value); + } + } + return params; +} + +function EndpointRow({ endpoint, topic }) { + const isRest = topic.type === 'rest'; + const meta = !isRest ? graphqlMeta[endpoint.name] : null; -function EndpointRow({ endpoint }) { - const meta = graphqlMeta[endpoint.name]; const [queryText, setQueryText] = useState(meta?.query || ''); const [variablesText, setVariablesText] = useState(meta?.variables || '{}'); - - const parseParams = (url) => { - const parts = url.split('?'); - if(parts.length < 2) return {}; - const sp = new URLSearchParams(parts[1]); - const obj = {}; - for(const [k, v] of sp.entries()) obj[k] = v; - return obj; - }; - - const [params, setParams] = useState(parseParams(endpoint.url)); + const [params, setParams] = useState(() => parseUrlParams(endpoint.url)); const [result, setResult] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [copied, setCopied] = useState(false); useEffect(() => { setQueryText(meta?.query || ''); setVariablesText(meta?.variables || '{}'); - }, [meta?.query, meta?.variables]); - - useEffect(() => { - setParams(parseParams(endpoint.url)); - }, [endpoint.url]); - - useEffect(() => { - try { - const parsed = variablesText?.trim() ? JSON.parse(variablesText) : {}; - if(endpoint.method === 'GET') { - const next = {}; - Object.keys(params).forEach(k => { - next[k] = parsed[k] !== undefined ? String(parsed[k]) : params[k]; - }); - setParams(next); - } - } catch(e) {} + setParams(parseUrlParams(endpoint.url)); + setResult(null); + setError(null); }, [endpoint.name]); + const copyResponse = async () => { + if (!result) return; + try { + await navigator.clipboard.writeText(JSON.stringify(result.data, null, 2)); + setCopied(true); setTimeout(() => setCopied(false), 1500); + } catch {} + }; + const execute = async () => { setError(null); setIsLoading(true); setResult(null); try { - const base = 'https://api.workolik.com'; - let requestUrl = base + endpoint.url; - const options = { - method: endpoint.method, - headers: { - 'x-hasura-admin-secret': 'nearle-admin-secret', - 'Content-Type': 'application/json' - } - }; - - if (endpoint.method === 'GET' && !endpoint.useGraphql) { - const emptyKeys = Object.entries(params).filter(([k,v]) => String(v).trim() === '').map(([k])=>k); - if (emptyKeys.length) { - setError('Required parameter(s): ' + emptyKeys.join(', ')); - setIsLoading(false); - return; - } + if (isRest) { + // REST: fetch directly (fiesta.nearle.app supports CORS) const path = endpoint.url.split('?')[0]; - const qs = new URLSearchParams(params).toString(); - requestUrl = base + path + (qs ? '?' + qs : ''); - } else { - const parsedVariables = variablesText?.trim() ? JSON.parse(variablesText) : {}; - requestUrl = base + '/v1/graphql'; - options.method = 'POST'; - options.body = JSON.stringify({ query: queryText, variables: parsedVariables }); - } + const isGet = endpoint.method === 'GET'; + let url = topic.baseUrl + endpoint.url; - const res = await fetch(requestUrl, options); - const data = await res.json(); - - setResult({ status: res.status, ok: res.ok, data }); - } catch(err) { + if (isGet && Object.keys(params).length > 0) { + const qs = new URLSearchParams(params).toString(); + url = topic.baseUrl + path + (qs ? '?' + qs : ''); + } + + const options = { + method: endpoint.method, + headers: { 'Content-Type': 'application/json' }, + }; + + if (!isGet && endpoint.body) { + options.body = typeof endpoint.body === 'string' + ? endpoint.body + : JSON.stringify(endpoint.body); + } + + const res = await fetch(url, options); + const text = await res.text(); + let data; + try { data = JSON.parse(text); } catch { data = text; } + setResult({ status: res.status, ok: res.ok, data }); + + } else { + // GraphQL / Hasura + const base = topic.baseUrl; + let requestUrl = base + endpoint.url; + const options = { + method: endpoint.method, + headers: { + 'x-hasura-admin-secret': 'nearle-admin-secret', + 'Content-Type': 'application/json', + }, + }; + + if (endpoint.method === 'GET' && !endpoint.useGraphql) { + const emptyKeys = Object.entries(params).filter(([, v]) => String(v).trim() === '').map(([k]) => k); + if (emptyKeys.length) { + setError('Required parameter(s): ' + emptyKeys.join(', ')); + setIsLoading(false); + return; + } + const path = endpoint.url.split('?')[0]; + const qs = new URLSearchParams(params).toString(); + requestUrl = base + path + (qs ? '?' + qs : ''); + } else { + const parsedVars = variablesText?.trim() ? JSON.parse(variablesText) : {}; + requestUrl = base + '/v1/graphql'; + options.method = 'POST'; + options.body = JSON.stringify({ query: queryText, variables: parsedVars }); + } + + const res = await fetch(requestUrl, options); + const data = await res.json(); + setResult({ status: res.status, ok: res.ok, data }); + } + } catch (err) { setError(String(err)); } finally { setIsLoading(false); @@ -88,16 +122,19 @@ function EndpointRow({ endpoint }) { }; 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' + 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'; + const urlPath = endpoint.url.split('?')[0]; + return (
- + {/* Left Column: Description & Parameters */}
@@ -114,10 +151,11 @@ function EndpointRow({ endpoint }) {
- https://api.workolik.com - {endpoint.url.split('?')[0]} + {topic.baseUrl} + {urlPath}
+ {/* Query params (GET endpoints) */} {endpoint.method === 'GET' && Object.keys(params).length > 0 && (

@@ -130,22 +168,21 @@ function EndpointRow({ endpoint }) { {key} string - { const newVal = e.target.value; - setParams(p => { - const next = {...p, [key]: newVal}; + setParams(p => ({ ...p, [key]: newVal })); + if (!isRest) { try { setVariablesText(JSON.stringify({ ...JSON.parse(variablesText || '{}'), [key]: isNaN(newVal) || newVal === '' ? newVal : Number(newVal) }, null, 2)); - } catch(err) {} - return next; - }); + } catch {} + } }} />

@@ -155,10 +192,10 @@ function EndpointRow({ endpoint }) { )}
- {/* Right Column: Execution & Code */} + {/* Right Column: Code Panel */}
- + {/* macOS window dots */}
@@ -167,23 +204,24 @@ function EndpointRow({ endpoint }) {
Request Payload
+ {/* Pane content */} {meta ? ( + // GraphQL editor: query + variables
GraphQL Query
-