@elizaos/plugin-shopify-ui 2.0.3-beta.5 → 2.0.3-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CustomersPanel.d.ts +11 -0
- package/dist/CustomersPanel.d.ts.map +1 -0
- package/dist/CustomersPanel.js +86 -0
- package/dist/CustomersPanel.js.map +1 -0
- package/dist/InventoryLevelsPanel.d.ts +10 -0
- package/dist/InventoryLevelsPanel.d.ts.map +1 -0
- package/dist/InventoryLevelsPanel.js +190 -0
- package/dist/InventoryLevelsPanel.js.map +1 -0
- package/dist/OrdersPanel.d.ts +12 -0
- package/dist/OrdersPanel.d.ts.map +1 -0
- package/dist/OrdersPanel.js +170 -0
- package/dist/OrdersPanel.js.map +1 -0
- package/dist/ProductsPanel.d.ts +13 -0
- package/dist/ProductsPanel.d.ts.map +1 -0
- package/dist/ProductsPanel.js +419 -0
- package/dist/ProductsPanel.js.map +1 -0
- package/dist/ShopifyAppView.d.ts +3 -0
- package/dist/ShopifyAppView.d.ts.map +1 -0
- package/dist/ShopifyAppView.helpers.d.ts +11 -0
- package/dist/ShopifyAppView.helpers.d.ts.map +1 -0
- package/dist/ShopifyAppView.helpers.js +59 -0
- package/dist/ShopifyAppView.helpers.js.map +1 -0
- package/dist/ShopifyAppView.interact.d.ts +2 -0
- package/dist/ShopifyAppView.interact.d.ts.map +1 -0
- package/dist/ShopifyAppView.interact.js +93 -0
- package/dist/ShopifyAppView.interact.js.map +1 -0
- package/dist/ShopifyAppView.js +667 -0
- package/dist/ShopifyAppView.js.map +1 -0
- package/dist/ShopifyView.d.ts +18 -0
- package/dist/ShopifyView.d.ts.map +1 -0
- package/dist/ShopifyView.js +143 -0
- package/dist/ShopifyView.js.map +1 -0
- package/dist/StoreOverviewCard.d.ts +13 -0
- package/dist/StoreOverviewCard.d.ts.map +1 -0
- package/dist/StoreOverviewCard.js +23 -0
- package/dist/StoreOverviewCard.js.map +1 -0
- package/dist/components/ShopifySpatialView.d.ts +57 -0
- package/dist/components/ShopifySpatialView.d.ts.map +1 -0
- package/dist/components/ShopifySpatialView.js +419 -0
- package/dist/components/ShopifySpatialView.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +94 -0
- package/dist/plugin.js.map +1 -0
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +6 -0
- package/dist/register-routes.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +37 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +17 -0
- package/dist/register.js.map +1 -0
- package/dist/routes.d.ts +18 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +518 -0
- package/dist/routes.js.map +1 -0
- package/dist/shopify-app.d.ts +11 -0
- package/dist/shopify-app.d.ts.map +1 -0
- package/dist/shopify-app.js +16 -0
- package/dist/shopify-app.js.map +1 -0
- package/dist/shopify-view-bundle.d.ts +3 -0
- package/dist/shopify-view-bundle.d.ts.map +1 -0
- package/dist/shopify-view-bundle.js +7 -0
- package/dist/shopify-view-bundle.js.map +1 -0
- package/dist/useShopifyDashboard.d.ts +118 -0
- package/dist/useShopifyDashboard.d.ts.map +1 -0
- package/dist/useShopifyDashboard.js +212 -0
- package/dist/useShopifyDashboard.js.map +1 -0
- package/dist/views/bundle.js +948 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts"],"sourcesContent":["/**\n * Shopify dashboard API routes.\n *\n * GET /api/shopify/status\n * GET /api/shopify/products?page=N&limit=N&q=Q\n * POST /api/shopify/products body: { title, vendor?, productType?, price? }\n * GET /api/shopify/orders?status=S&limit=N\n * GET /api/shopify/inventory\n * POST /api/shopify/inventory/:itemId/adjust body: { delta, locationId? }\n * GET /api/shopify/customers?q=Q&limit=N\n *\n * Credentials are read from process.env:\n * SHOPIFY_STORE_DOMAIN — e.g. mystore.myshopify.com\n * SHOPIFY_ACCESS_TOKEN — Shopify Admin API access token\n */\n\nimport type http from \"node:http\";\nimport { sendJson, sendJsonError } from \"@elizaos/app-core/api/response\";\nimport { logger } from \"@elizaos/core\";\n\nconst API_VERSION = \"2025-04\";\nconst ORDER_STATUS_FILTER_VALUES = [\n \"any\",\n \"paid\",\n \"pending\",\n \"refunded\",\n \"partially_refunded\",\n] as const;\nconst ORDER_STATUS_FILTERS = new Set<string>(ORDER_STATUS_FILTER_VALUES);\n\n/* ── Config resolution ─────────────────────────────────────────────── */\n\ninterface ShopifyConfig {\n storeDomain: string;\n accessToken: string;\n}\n\nfunction resolveShopifyConfig(): ShopifyConfig | null {\n const storeDomain = process.env.SHOPIFY_STORE_DOMAIN?.trim() ?? null;\n const accessToken = process.env.SHOPIFY_ACCESS_TOKEN?.trim() ?? null;\n if (!storeDomain || !accessToken) return null;\n return { storeDomain, accessToken };\n}\n\n/* ── GraphQL helper ────────────────────────────────────────────────── */\n\nasync function shopifyGql<T>(\n config: ShopifyConfig,\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n const domain = config.storeDomain\n .replace(/^https?:\\/\\//, \"\")\n .replace(/\\/$/, \"\");\n const url = `https://${domain}/admin/api/${API_VERSION}/graphql.json`;\n\n const resp = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Shopify-Access-Token\": config.accessToken,\n },\n body: JSON.stringify({ query, variables }),\n signal: AbortSignal.timeout(15_000),\n });\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => \"\");\n throw new Error(`Shopify API ${resp.status}: ${text.slice(0, 200)}`);\n }\n\n const json = (await resp.json()) as {\n data?: T;\n errors?: Array<{ message: string }>;\n };\n if (json.errors?.length) {\n throw new Error(\n `Shopify GraphQL: ${json.errors.map((e) => e.message).join(\", \")}`,\n );\n }\n return json.data as T;\n}\n\n/* ── Route handler ─────────────────────────────────────────────────── */\n\nexport async function handleShopifyRoute(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n method: string,\n): Promise<boolean> {\n if (!pathname.startsWith(\"/api/shopify\")) return false;\n\n const config = resolveShopifyConfig();\n\n // ── GET /api/shopify/status ──────────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/shopify/status\") {\n if (!config) {\n sendJson(res, 200, { connected: false, shop: null });\n return true;\n }\n try {\n const data = await shopifyGql<{\n shop: {\n name: string;\n myshopifyDomain: string;\n plan: { displayName: string };\n email: string;\n currencyCode: string;\n productsCount?: { count: number };\n };\n }>(\n config,\n `{ shop { name myshopifyDomain plan { displayName } email currencyCode } }`,\n );\n sendJson(res, 200, {\n connected: true,\n shop: {\n name: data.shop.name,\n domain: data.shop.myshopifyDomain,\n plan: data.shop.plan.displayName,\n email: data.shop.email,\n currencyCode: data.shop.currencyCode,\n ...(typeof data.shop.productsCount?.count === \"number\"\n ? { productCount: data.shop.productsCount.count }\n : {}),\n },\n });\n } catch (err) {\n logger.error(\n `[shopify/status] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJson(res, 200, {\n connected: false,\n shop: null,\n error: err instanceof Error ? err.message : \"Connection failed\",\n });\n }\n return true;\n }\n\n // All routes below require valid Shopify credentials.\n if (!config) {\n sendJsonError(\n res,\n 404,\n \"Shopify not configured (SHOPIFY_STORE_DOMAIN / SHOPIFY_ACCESS_TOKEN not set)\",\n );\n return true;\n }\n\n // ── GET /api/shopify/products ────────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/shopify/products\") {\n try {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const page = Math.max(1, Number(url.searchParams.get(\"page\") ?? \"1\"));\n const limit = Math.min(\n 50,\n Math.max(1, Number(url.searchParams.get(\"limit\") ?? \"20\")),\n );\n const search = url.searchParams.get(\"q\")?.trim() || null;\n const countData = await shopifyGql<{\n productsCount: { count: number };\n }>(\n config,\n `query CountProducts($query: String) {\n productsCount(query: $query) { count }\n }`,\n { query: search },\n );\n\n let after: string | null = null;\n let pageProducts: Array<{\n cursor: string;\n node: {\n id: string;\n title: string;\n status: string;\n productType: string;\n vendor: string;\n totalInventory: number;\n updatedAt: string;\n featuredImage: { url: string } | null;\n priceRangeV2: {\n minVariantPrice: { amount: string };\n maxVariantPrice: { amount: string };\n };\n };\n }> = [];\n type ShopifyProductsPageResponse = {\n products: {\n edges: Array<{\n cursor: string;\n node: {\n id: string;\n title: string;\n status: string;\n productType: string;\n vendor: string;\n totalInventory: number;\n updatedAt: string;\n featuredImage: { url: string } | null;\n priceRangeV2: {\n minVariantPrice: { amount: string };\n maxVariantPrice: { amount: string };\n };\n };\n }>;\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n };\n\n for (let currentPage = 1; currentPage <= page; currentPage++) {\n const data: ShopifyProductsPageResponse =\n await shopifyGql<ShopifyProductsPageResponse>(\n config,\n `query ListProductsPage($first: Int!, $after: String, $query: String) {\n products(first: $first, after: $after, query: $query, sortKey: TITLE) {\n edges {\n cursor\n node {\n id title status productType vendor totalInventory updatedAt\n featuredImage { url }\n priceRangeV2 { minVariantPrice { amount } maxVariantPrice { amount } }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n }`,\n {\n first: limit,\n after,\n query: search,\n },\n );\n\n if (currentPage === page) {\n pageProducts = data.products.edges;\n break;\n }\n\n if (\n !data.products.pageInfo.hasNextPage ||\n !data.products.pageInfo.endCursor\n ) {\n pageProducts = [];\n break;\n }\n\n after = data.products.pageInfo.endCursor;\n }\n\n const products = pageProducts.map((edge) => ({\n id: edge.node.id,\n title: edge.node.title,\n status: edge.node.status as \"ACTIVE\" | \"DRAFT\" | \"ARCHIVED\",\n productType: edge.node.productType,\n vendor: edge.node.vendor,\n totalInventory: edge.node.totalInventory,\n priceRange: {\n min: edge.node.priceRangeV2.minVariantPrice.amount,\n max: edge.node.priceRangeV2.maxVariantPrice.amount,\n },\n imageUrl: edge.node.featuredImage?.url ?? null,\n updatedAt: edge.node.updatedAt,\n }));\n\n sendJson(res, 200, {\n products,\n total: countData.productsCount.count,\n page,\n pageSize: limit,\n });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to fetch products\";\n logger.error(\n `[shopify/products] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n // ── POST /api/shopify/products ───────────────────────────────────\n if (method === \"POST\" && pathname === \"/api/shopify/products\") {\n try {\n const raw = await readBody(req);\n const input = JSON.parse(raw) as {\n title?: string;\n productType?: string;\n vendor?: string;\n price?: string;\n };\n if (!input.title?.trim()) {\n sendJsonError(res, 400, \"title is required\");\n return true;\n }\n\n const data = await shopifyGql<{\n productCreate: {\n product: {\n id: string;\n title: string;\n status: string;\n productType: string;\n vendor: string;\n totalInventory: number;\n updatedAt: string;\n featuredImage: { url: string } | null;\n } | null;\n userErrors: Array<{ field: string[]; message: string }>;\n };\n }>(\n config,\n `mutation CreateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n title\n status\n productType\n vendor\n totalInventory\n updatedAt\n featuredImage { url }\n }\n userErrors { field message }\n }\n }`,\n {\n input: {\n title: input.title.trim(),\n productType: input.productType?.trim() ?? \"\",\n vendor: input.vendor?.trim() ?? \"\",\n status: \"DRAFT\",\n },\n },\n );\n\n if (data.productCreate.userErrors.length) {\n sendJsonError(\n res,\n 422,\n data.productCreate.userErrors\n .map((e) => `${e.field.join(\".\")}: ${e.message}`)\n .join(\"; \"),\n );\n return true;\n }\n\n const product = data.productCreate.product;\n if (!product) {\n sendJsonError(res, 500, \"Product create returned no product\");\n return true;\n }\n\n sendJson(res, 201, {\n id: product.id,\n title: product.title,\n status: product.status,\n productType: product.productType,\n vendor: product.vendor,\n totalInventory: product.totalInventory,\n updatedAt: product.updatedAt,\n imageUrl: product.featuredImage?.url ?? null,\n priceRange: {\n min: input.price ?? \"0.00\",\n max: input.price ?? \"0.00\",\n },\n });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to create product\";\n logger.error(\n `[shopify/products/create] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n // ── GET /api/shopify/orders ──────────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/shopify/orders\") {\n try {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const limit = Math.min(\n 50,\n Math.max(1, Number(url.searchParams.get(\"limit\") ?? \"20\")),\n );\n const status = (url.searchParams.get(\"status\") ?? \"any\")\n .trim()\n .toLowerCase();\n if (!ORDER_STATUS_FILTERS.has(status)) {\n sendJsonError(res, 400, `Unsupported order status filter: ${status}`);\n return true;\n }\n const queryFilter =\n status !== \"any\" ? `financial_status:${status}` : null;\n\n const data = await shopifyGql<{\n orders: {\n edges: Array<{\n node: {\n id: string;\n name: string;\n email: string;\n createdAt: string;\n displayFinancialStatus: string;\n displayFulfillmentStatus: string | null;\n totalPriceSet: {\n shopMoney: { amount: string; currencyCode: string };\n };\n lineItems: { edges: Array<unknown> };\n };\n }>;\n };\n ordersCount: { count: number };\n }>(\n config,\n `query ListOrders($first: Int!, $query: String) {\n orders(first: $first, query: $query, sortKey: CREATED_AT, reverse: true) {\n edges {\n node {\n id name email createdAt\n displayFinancialStatus displayFulfillmentStatus\n totalPriceSet { shopMoney { amount currencyCode } }\n lineItems(first: 1) { edges { node { id } } }\n }\n }\n }\n ordersCount { count }\n }`,\n { first: limit, query: queryFilter },\n );\n\n const orders = data.orders.edges.map((edge) => ({\n id: edge.node.id,\n name: edge.node.name,\n email: edge.node.email ?? \"\",\n totalPrice: edge.node.totalPriceSet.shopMoney.amount,\n currencyCode: edge.node.totalPriceSet.shopMoney.currencyCode,\n fulfillmentStatus: edge.node.displayFulfillmentStatus as\n | \"FULFILLED\"\n | \"UNFULFILLED\"\n | \"PARTIALLY_FULFILLED\"\n | null,\n financialStatus: edge.node.displayFinancialStatus as\n | \"PAID\"\n | \"PENDING\"\n | \"REFUNDED\"\n | \"PARTIALLY_REFUNDED\",\n createdAt: edge.node.createdAt,\n lineItemCount: edge.node.lineItems.edges.length,\n }));\n\n sendJson(res, 200, { orders, total: data.ordersCount.count });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to fetch orders\";\n logger.error(\n `[shopify/orders] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n // ── GET /api/shopify/inventory ───────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/shopify/inventory\") {\n try {\n const data = await shopifyGql<{\n products: {\n edges: Array<{\n node: {\n title: string;\n variants: {\n edges: Array<{\n node: {\n id: string;\n title: string;\n sku: string;\n inventoryItem: {\n id: string;\n inventoryLevels: {\n edges: Array<{\n node: {\n available: number;\n location: { id: string; name: string };\n };\n }>;\n };\n };\n };\n }>;\n };\n };\n }>;\n };\n locations: {\n edges: Array<{ node: { name: string; isActive: boolean } }>;\n };\n }>(\n config,\n `{\n products(first: 50) {\n edges {\n node {\n title\n variants(first: 10) {\n edges {\n node {\n id title sku\n inventoryItem {\n id\n inventoryLevels(first: 10) {\n edges { node { available location { id name } } }\n }\n }\n }\n }\n }\n }\n }\n }\n locations(first: 20) {\n edges { node { name isActive } }\n }\n }`,\n );\n\n const items: Array<{\n id: string;\n sku: string;\n productTitle: string;\n variantTitle: string;\n locationId: string | null;\n locationName: string;\n available: number;\n incoming: number;\n }> = [];\n\n for (const productEdge of data.products.edges) {\n for (const variantEdge of productEdge.node.variants.edges) {\n const variant = variantEdge.node;\n const levels = variant.inventoryItem.inventoryLevels.edges;\n if (levels.length === 0) {\n items.push({\n id: variant.inventoryItem.id,\n sku: variant.sku ?? \"\",\n productTitle: productEdge.node.title,\n variantTitle:\n variant.title === \"Default Title\" ? \"\" : variant.title,\n locationId: null,\n locationName: \"\",\n available: 0,\n incoming: 0,\n });\n continue;\n }\n\n for (const levelEdge of levels) {\n items.push({\n id: variant.inventoryItem.id,\n sku: variant.sku ?? \"\",\n productTitle: productEdge.node.title,\n variantTitle:\n variant.title === \"Default Title\" ? \"\" : variant.title,\n locationId: levelEdge.node.location.id,\n locationName: levelEdge.node.location.name,\n available: levelEdge.node.available,\n incoming: 0,\n });\n }\n }\n }\n\n const locations = data.locations.edges\n .filter((edge) => edge.node.isActive)\n .map((edge) => edge.node.name);\n\n sendJson(res, 200, { items, locations });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to fetch inventory\";\n logger.error(\n `[shopify/inventory] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n // ── POST /api/shopify/inventory/:itemId/adjust ──────────────────\n const adjustMatch = pathname.match(\n /^\\/api\\/shopify\\/inventory\\/(.+)\\/adjust$/,\n );\n if (adjustMatch && method === \"POST\") {\n try {\n const raw = await readBody(req);\n const body = JSON.parse(raw) as {\n delta?: number;\n locationId?: string | null;\n };\n const delta = Number(body.delta);\n if (!Number.isInteger(delta) || delta === 0) {\n sendJsonError(res, 400, \"delta must be a non-zero integer\");\n return true;\n }\n\n const inventoryItemId = adjustMatch[1];\n const requestedLocationId =\n typeof body.locationId === \"string\" && body.locationId.trim()\n ? body.locationId.trim()\n : null;\n const itemData = await shopifyGql<{\n inventoryItem: {\n id: string;\n inventoryLevels: {\n edges: Array<{\n node: {\n id: string;\n location: { id: string; name: string };\n };\n }>;\n };\n } | null;\n }>(\n config,\n `query GetInventoryItem($id: ID!) {\n inventoryItem(id: $id) {\n id\n inventoryLevels(first: 5) {\n edges { node { id location { id name } } }\n }\n }\n }`,\n { id: inventoryItemId },\n );\n\n if (!itemData.inventoryItem) {\n sendJsonError(res, 404, `Inventory item not found: ${inventoryItemId}`);\n return true;\n }\n\n const levels = itemData.inventoryItem.inventoryLevels.edges;\n if (levels.length === 0) {\n sendJsonError(\n res,\n 422,\n \"No inventory levels found for this item — item may not be tracked\",\n );\n return true;\n }\n\n let locationId = requestedLocationId;\n if (locationId) {\n const matchingLevel = levels.find(\n (level) => level.node.location.id === locationId,\n );\n if (!matchingLevel) {\n sendJsonError(\n res,\n 400,\n `Location ${locationId} is not valid for inventory item ${inventoryItemId}`,\n );\n return true;\n }\n } else if (levels.length === 1) {\n locationId = levels[0].node.location.id;\n } else {\n sendJsonError(\n res,\n 400,\n \"locationId is required when an inventory item exists in multiple locations\",\n );\n return true;\n }\n\n const adjustData = await shopifyGql<{\n inventoryAdjustQuantities: {\n inventoryAdjustmentGroup: { reason: string } | null;\n userErrors: Array<{ field: string[]; message: string }>;\n };\n }>(\n config,\n `mutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {\n inventoryAdjustQuantities(input: $input) {\n inventoryAdjustmentGroup { reason }\n userErrors { field message }\n }\n }`,\n {\n input: {\n reason: \"correction\",\n name: \"available\",\n changes: [\n {\n inventoryItemId,\n locationId,\n delta,\n },\n ],\n },\n },\n );\n\n if (adjustData.inventoryAdjustQuantities.userErrors.length) {\n sendJsonError(\n res,\n 422,\n adjustData.inventoryAdjustQuantities.userErrors\n .map((error) => error.message)\n .join(\"; \"),\n );\n return true;\n }\n\n sendJson(res, 200, { ok: true, locationId });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to adjust inventory\";\n logger.error(\n `[shopify/inventory/adjust] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n // ── GET /api/shopify/customers ───────────────────────────────────\n if (method === \"GET\" && pathname === \"/api/shopify/customers\") {\n try {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const limit = Math.min(\n 50,\n Math.max(1, Number(url.searchParams.get(\"limit\") ?? \"20\")),\n );\n const search = url.searchParams.get(\"q\")?.trim() || null;\n\n const data = await shopifyGql<{\n customers: {\n edges: Array<{\n node: {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n // Admin API 2025-04 removed Customer.ordersCount / totalSpentV2.\n // numberOfOrders is UnsignedInt64 (serialized as a string in JSON);\n // amountSpent is MoneyV2. Verified against the 2025-04 schema.\n numberOfOrders: string;\n amountSpent: { amount: string; currencyCode: string };\n createdAt: string;\n };\n }>;\n };\n customersCount: { count: number };\n }>(\n config,\n `query ListCustomers($first: Int!, $query: String) {\n customers(first: $first, query: $query) {\n edges {\n node {\n id firstName lastName email numberOfOrders\n amountSpent { amount currencyCode }\n createdAt\n }\n }\n }\n customersCount { count }\n }`,\n { first: limit, query: search },\n );\n\n const customers = data.customers.edges.map((edge) => ({\n id: edge.node.id,\n firstName: edge.node.firstName ?? \"\",\n lastName: edge.node.lastName ?? \"\",\n email: edge.node.email ?? \"\",\n ordersCount: Number(edge.node.numberOfOrders ?? 0),\n totalSpent: edge.node.amountSpent.amount,\n currencyCode: edge.node.amountSpent.currencyCode,\n createdAt: edge.node.createdAt,\n }));\n\n sendJson(res, 200, { customers, total: data.customersCount.count });\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"Failed to fetch customers\";\n logger.error(\n `[shopify/customers] ${err instanceof Error ? err.message : String(err)}`,\n );\n sendJsonError(res, 500, message);\n }\n return true;\n }\n\n return false;\n}\n\n/* ── Helpers ───────────────────────────────────────────────────────── */\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify App — @elizaos/plugin-shopify-ui
|
|
3
|
+
*
|
|
4
|
+
* Full-screen overlay app for Shopify store management: products, orders,
|
|
5
|
+
* inventory, and customers. Implements the OverlayApp API so the host shell
|
|
6
|
+
* can launch it like any other overlay.
|
|
7
|
+
*/
|
|
8
|
+
import type { OverlayApp } from "@elizaos/ui";
|
|
9
|
+
export declare const SHOPIFY_APP_NAME = "@elizaos/plugin-shopify-ui";
|
|
10
|
+
export declare const shopifyApp: OverlayApp;
|
|
11
|
+
//# sourceMappingURL=shopify-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopify-app.d.ts","sourceRoot":"","sources":["../src/shopify-app.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAE7D,eAAO,MAAM,UAAU,EAAE,UASxB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerOverlayApp } from "@elizaos/ui";
|
|
2
|
+
const SHOPIFY_APP_NAME = "@elizaos/plugin-shopify-ui";
|
|
3
|
+
const shopifyApp = {
|
|
4
|
+
name: SHOPIFY_APP_NAME,
|
|
5
|
+
displayName: "Shopify",
|
|
6
|
+
description: "Manage your Shopify store \u2014 products, orders, inventory, customers",
|
|
7
|
+
category: "utility",
|
|
8
|
+
icon: null,
|
|
9
|
+
loader: () => import("./ShopifyAppView.js").then((m) => ({ default: m.ShopifyAppView }))
|
|
10
|
+
};
|
|
11
|
+
registerOverlayApp(shopifyApp);
|
|
12
|
+
export {
|
|
13
|
+
SHOPIFY_APP_NAME,
|
|
14
|
+
shopifyApp
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=shopify-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shopify-app.ts"],"sourcesContent":["/**\n * Shopify App — @elizaos/plugin-shopify-ui\n *\n * Full-screen overlay app for Shopify store management: products, orders,\n * inventory, and customers. Implements the OverlayApp API so the host shell\n * can launch it like any other overlay.\n */\n\nimport type { OverlayApp } from \"@elizaos/ui\";\nimport { registerOverlayApp } from \"@elizaos/ui\";\n\nexport const SHOPIFY_APP_NAME = \"@elizaos/plugin-shopify-ui\";\n\nexport const shopifyApp: OverlayApp = {\n name: SHOPIFY_APP_NAME,\n displayName: \"Shopify\",\n description:\n \"Manage your Shopify store — products, orders, inventory, customers\",\n category: \"utility\",\n icon: null,\n loader: () =>\n import(\"./ShopifyAppView.js\").then((m) => ({ default: m.ShopifyAppView })),\n};\n\n// Self-register at import time\nregisterOverlayApp(shopifyApp);\n"],"mappings":"AASA,SAAS,0BAA0B;AAE5B,MAAM,mBAAmB;AAEzB,MAAM,aAAyB;AAAA,EACpC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ,MACN,OAAO,qBAAqB,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE;AAC7E;AAGA,mBAAmB,UAAU;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopify-view-bundle.d.ts","sourceRoot":"","sources":["../src/shopify-view-bundle.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shopify-view-bundle.ts"],"sourcesContent":["// Vite view-bundle entry. Re-exports the unified spatial view component plus\n// the `interact` capability handler so the built bundle (dist/views/bundle.js)\n// exposes the named exports the view loader reads (`ShopifyView`, `interact`).\n// Kept separate from ShopifyView.tsx so that file exports only React components\n// and stays Fast-Refresh-compatible.\nexport { interact } from \"./ShopifyAppView.interact.js\";\nexport { ShopifyView } from \"./ShopifyView.js\";\n"],"mappings":"AAKA,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;","names":[]}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useShopifyDashboard — data hook for the Shopify overlay app.
|
|
3
|
+
*
|
|
4
|
+
* Polls all Shopify API endpoints and exposes typed state for each panel.
|
|
5
|
+
* Handles route 404 responses gracefully as "disconnected".
|
|
6
|
+
*/
|
|
7
|
+
export interface ShopifyStatus {
|
|
8
|
+
connected: boolean;
|
|
9
|
+
shop: {
|
|
10
|
+
name: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
plan: string;
|
|
13
|
+
email: string;
|
|
14
|
+
currencyCode: string;
|
|
15
|
+
} | null;
|
|
16
|
+
}
|
|
17
|
+
export interface ShopifyProduct {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
status: "ACTIVE" | "DRAFT" | "ARCHIVED";
|
|
21
|
+
productType: string;
|
|
22
|
+
vendor: string;
|
|
23
|
+
totalInventory: number;
|
|
24
|
+
priceRange: {
|
|
25
|
+
min: string;
|
|
26
|
+
max: string;
|
|
27
|
+
};
|
|
28
|
+
imageUrl: string | null;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ShopifyOrder {
|
|
32
|
+
id: string;
|
|
33
|
+
/** e.g. "#1001" */
|
|
34
|
+
name: string;
|
|
35
|
+
email: string;
|
|
36
|
+
totalPrice: string;
|
|
37
|
+
currencyCode: string;
|
|
38
|
+
fulfillmentStatus: "FULFILLED" | "UNFULFILLED" | "PARTIALLY_FULFILLED" | null;
|
|
39
|
+
financialStatus: "PAID" | "PENDING" | "REFUNDED" | "PARTIALLY_REFUNDED";
|
|
40
|
+
createdAt: string;
|
|
41
|
+
lineItemCount: number;
|
|
42
|
+
}
|
|
43
|
+
export interface ShopifyInventoryItem {
|
|
44
|
+
id: string;
|
|
45
|
+
sku: string;
|
|
46
|
+
productTitle: string;
|
|
47
|
+
variantTitle: string;
|
|
48
|
+
locationId: string | null;
|
|
49
|
+
locationName: string;
|
|
50
|
+
available: number;
|
|
51
|
+
incoming: number;
|
|
52
|
+
}
|
|
53
|
+
export interface ShopifyCustomer {
|
|
54
|
+
id: string;
|
|
55
|
+
firstName: string;
|
|
56
|
+
lastName: string;
|
|
57
|
+
email: string;
|
|
58
|
+
ordersCount: number;
|
|
59
|
+
totalSpent: string;
|
|
60
|
+
currencyCode: string;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ShopifyProductsResponse {
|
|
64
|
+
products: ShopifyProduct[];
|
|
65
|
+
total: number;
|
|
66
|
+
page: number;
|
|
67
|
+
pageSize: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ShopifyOrdersResponse {
|
|
70
|
+
orders: ShopifyOrder[];
|
|
71
|
+
total: number;
|
|
72
|
+
}
|
|
73
|
+
export interface ShopifyInventoryResponse {
|
|
74
|
+
items: ShopifyInventoryItem[];
|
|
75
|
+
locations: string[];
|
|
76
|
+
}
|
|
77
|
+
export interface ShopifyCustomersResponse {
|
|
78
|
+
customers: ShopifyCustomer[];
|
|
79
|
+
total: number;
|
|
80
|
+
}
|
|
81
|
+
export interface ShopifyAggregateCounts {
|
|
82
|
+
productCount: number;
|
|
83
|
+
orderCount: number;
|
|
84
|
+
customerCount: number;
|
|
85
|
+
}
|
|
86
|
+
export interface UseShopifyDashboardReturn {
|
|
87
|
+
status: ShopifyStatus | null;
|
|
88
|
+
statusLoading: boolean;
|
|
89
|
+
statusError: string | null;
|
|
90
|
+
products: ShopifyProduct[];
|
|
91
|
+
productsTotal: number;
|
|
92
|
+
productsPage: number;
|
|
93
|
+
productsLoading: boolean;
|
|
94
|
+
productsError: string | null;
|
|
95
|
+
productSearch: string;
|
|
96
|
+
setProductSearch: (q: string) => void;
|
|
97
|
+
setProductsPage: (page: number) => void;
|
|
98
|
+
orders: ShopifyOrder[];
|
|
99
|
+
ordersTotal: number;
|
|
100
|
+
ordersLoading: boolean;
|
|
101
|
+
ordersError: string | null;
|
|
102
|
+
orderStatusFilter: string;
|
|
103
|
+
setOrderStatusFilter: (s: string) => void;
|
|
104
|
+
inventoryItems: ShopifyInventoryItem[];
|
|
105
|
+
inventoryLocations: string[];
|
|
106
|
+
inventoryLoading: boolean;
|
|
107
|
+
inventoryError: string | null;
|
|
108
|
+
customers: ShopifyCustomer[];
|
|
109
|
+
customersTotal: number;
|
|
110
|
+
customersLoading: boolean;
|
|
111
|
+
customersError: string | null;
|
|
112
|
+
customerSearch: string;
|
|
113
|
+
setCustomerSearch: (q: string) => void;
|
|
114
|
+
counts: ShopifyAggregateCounts;
|
|
115
|
+
refresh: () => void;
|
|
116
|
+
}
|
|
117
|
+
export declare function useShopifyDashboard(): UseShopifyDashboardReturn;
|
|
118
|
+
//# sourceMappingURL=useShopifyDashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useShopifyDashboard.d.ts","sourceRoot":"","sources":["../src/useShopifyDashboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
const PAGE_SIZE = 20;
|
|
3
|
+
const POLL_INTERVAL_MS = 3e4;
|
|
4
|
+
async function fetchJson(url) {
|
|
5
|
+
const res = await fetch(url);
|
|
6
|
+
if (res.status === 404) return null;
|
|
7
|
+
if (!res.ok) {
|
|
8
|
+
const text = await res.text().catch(() => "Unknown error");
|
|
9
|
+
throw new Error(`${res.status}: ${text}`);
|
|
10
|
+
}
|
|
11
|
+
return res.json();
|
|
12
|
+
}
|
|
13
|
+
function useShopifyDashboard() {
|
|
14
|
+
const [status, setStatus] = useState(null);
|
|
15
|
+
const [statusLoading, setStatusLoading] = useState(true);
|
|
16
|
+
const [statusError, setStatusError] = useState(null);
|
|
17
|
+
const [productsData, setProductsData] = useState(null);
|
|
18
|
+
const [productsLoading, setProductsLoading] = useState(false);
|
|
19
|
+
const [productsError, setProductsError] = useState(null);
|
|
20
|
+
const [productsPage, setProductsPage] = useState(1);
|
|
21
|
+
const [productSearch, setProductSearch] = useState("");
|
|
22
|
+
const [ordersData, setOrdersData] = useState(
|
|
23
|
+
null
|
|
24
|
+
);
|
|
25
|
+
const [ordersLoading, setOrdersLoading] = useState(false);
|
|
26
|
+
const [ordersError, setOrdersError] = useState(null);
|
|
27
|
+
const [orderStatusFilter, setOrderStatusFilter] = useState("any");
|
|
28
|
+
const [inventoryData, setInventoryData] = useState(null);
|
|
29
|
+
const [inventoryLoading, setInventoryLoading] = useState(false);
|
|
30
|
+
const [inventoryError, setInventoryError] = useState(null);
|
|
31
|
+
const [customersData, setCustomersData] = useState(null);
|
|
32
|
+
const [customersLoading, setCustomersLoading] = useState(false);
|
|
33
|
+
const [customersError, setCustomersError] = useState(null);
|
|
34
|
+
const [customerSearch, setCustomerSearch] = useState("");
|
|
35
|
+
const [tick, setTick] = useState(0);
|
|
36
|
+
const pollRef = useRef(null);
|
|
37
|
+
const refresh = useCallback(() => {
|
|
38
|
+
setTick((n) => n + 1);
|
|
39
|
+
}, []);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
pollRef.current = setInterval(refresh, POLL_INTERVAL_MS);
|
|
42
|
+
return () => {
|
|
43
|
+
if (pollRef.current !== null) clearInterval(pollRef.current);
|
|
44
|
+
};
|
|
45
|
+
}, [refresh]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
setStatusLoading(true);
|
|
49
|
+
setStatusError(null);
|
|
50
|
+
fetchJson("/api/shopify/status").then((data) => {
|
|
51
|
+
if (cancelled) return;
|
|
52
|
+
setStatus(
|
|
53
|
+
data ?? {
|
|
54
|
+
connected: false,
|
|
55
|
+
shop: null
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
if (cancelled) return;
|
|
60
|
+
setStatusError(
|
|
61
|
+
err instanceof Error ? err.message : "Failed to load Shopify status."
|
|
62
|
+
);
|
|
63
|
+
setStatus({ connected: false, shop: null });
|
|
64
|
+
}).finally(() => {
|
|
65
|
+
if (!cancelled) setStatusLoading(false);
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
};
|
|
70
|
+
}, [tick]);
|
|
71
|
+
const connected = status?.connected ?? false;
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!connected) return;
|
|
74
|
+
let cancelled = false;
|
|
75
|
+
setProductsLoading(true);
|
|
76
|
+
setProductsError(null);
|
|
77
|
+
const params = new URLSearchParams({
|
|
78
|
+
page: String(productsPage),
|
|
79
|
+
limit: String(PAGE_SIZE),
|
|
80
|
+
q: productSearch
|
|
81
|
+
});
|
|
82
|
+
fetchJson(`/api/shopify/products?${params}`).then((data) => {
|
|
83
|
+
if (cancelled) return;
|
|
84
|
+
setProductsData(
|
|
85
|
+
data ?? {
|
|
86
|
+
products: [],
|
|
87
|
+
total: 0,
|
|
88
|
+
page: productsPage,
|
|
89
|
+
pageSize: PAGE_SIZE
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}).catch((err) => {
|
|
93
|
+
if (cancelled) return;
|
|
94
|
+
setProductsError(
|
|
95
|
+
err instanceof Error ? err.message : "Failed to load products."
|
|
96
|
+
);
|
|
97
|
+
}).finally(() => {
|
|
98
|
+
if (!cancelled) setProductsLoading(false);
|
|
99
|
+
});
|
|
100
|
+
return () => {
|
|
101
|
+
cancelled = true;
|
|
102
|
+
};
|
|
103
|
+
}, [connected, productsPage, productSearch, tick]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!connected) return;
|
|
106
|
+
let cancelled = false;
|
|
107
|
+
setOrdersLoading(true);
|
|
108
|
+
setOrdersError(null);
|
|
109
|
+
const params = new URLSearchParams({
|
|
110
|
+
status: orderStatusFilter,
|
|
111
|
+
limit: String(PAGE_SIZE)
|
|
112
|
+
});
|
|
113
|
+
fetchJson(`/api/shopify/orders?${params}`).then((data) => {
|
|
114
|
+
if (cancelled) return;
|
|
115
|
+
setOrdersData(data ?? { orders: [], total: 0 });
|
|
116
|
+
}).catch((err) => {
|
|
117
|
+
if (cancelled) return;
|
|
118
|
+
setOrdersError(
|
|
119
|
+
err instanceof Error ? err.message : "Failed to load orders."
|
|
120
|
+
);
|
|
121
|
+
}).finally(() => {
|
|
122
|
+
if (!cancelled) setOrdersLoading(false);
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
cancelled = true;
|
|
126
|
+
};
|
|
127
|
+
}, [connected, orderStatusFilter, tick]);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (!connected) return;
|
|
130
|
+
let cancelled = false;
|
|
131
|
+
setInventoryLoading(true);
|
|
132
|
+
setInventoryError(null);
|
|
133
|
+
fetchJson("/api/shopify/inventory").then((data) => {
|
|
134
|
+
if (cancelled) return;
|
|
135
|
+
setInventoryData(data ?? { items: [], locations: [] });
|
|
136
|
+
}).catch((err) => {
|
|
137
|
+
if (cancelled) return;
|
|
138
|
+
setInventoryError(
|
|
139
|
+
err instanceof Error ? err.message : "Failed to load inventory."
|
|
140
|
+
);
|
|
141
|
+
}).finally(() => {
|
|
142
|
+
if (!cancelled) setInventoryLoading(false);
|
|
143
|
+
});
|
|
144
|
+
return () => {
|
|
145
|
+
cancelled = true;
|
|
146
|
+
};
|
|
147
|
+
}, [connected, tick]);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!connected) return;
|
|
150
|
+
let cancelled = false;
|
|
151
|
+
setCustomersLoading(true);
|
|
152
|
+
setCustomersError(null);
|
|
153
|
+
const params = new URLSearchParams({
|
|
154
|
+
q: customerSearch,
|
|
155
|
+
limit: String(PAGE_SIZE)
|
|
156
|
+
});
|
|
157
|
+
fetchJson(`/api/shopify/customers?${params}`).then((data) => {
|
|
158
|
+
if (cancelled) return;
|
|
159
|
+
setCustomersData(data ?? { customers: [], total: 0 });
|
|
160
|
+
}).catch((err) => {
|
|
161
|
+
if (cancelled) return;
|
|
162
|
+
setCustomersError(
|
|
163
|
+
err instanceof Error ? err.message : "Failed to load customers."
|
|
164
|
+
);
|
|
165
|
+
}).finally(() => {
|
|
166
|
+
if (!cancelled) setCustomersLoading(false);
|
|
167
|
+
});
|
|
168
|
+
return () => {
|
|
169
|
+
cancelled = true;
|
|
170
|
+
};
|
|
171
|
+
}, [connected, customerSearch, tick]);
|
|
172
|
+
const counts = {
|
|
173
|
+
productCount: productsData?.total ?? 0,
|
|
174
|
+
orderCount: ordersData?.total ?? 0,
|
|
175
|
+
customerCount: customersData?.total ?? 0
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
status,
|
|
179
|
+
statusLoading,
|
|
180
|
+
statusError,
|
|
181
|
+
products: productsData?.products ?? [],
|
|
182
|
+
productsTotal: productsData?.total ?? 0,
|
|
183
|
+
productsPage,
|
|
184
|
+
productsLoading,
|
|
185
|
+
productsError,
|
|
186
|
+
productSearch,
|
|
187
|
+
setProductSearch,
|
|
188
|
+
setProductsPage,
|
|
189
|
+
orders: ordersData?.orders ?? [],
|
|
190
|
+
ordersTotal: ordersData?.total ?? 0,
|
|
191
|
+
ordersLoading,
|
|
192
|
+
ordersError,
|
|
193
|
+
orderStatusFilter,
|
|
194
|
+
setOrderStatusFilter,
|
|
195
|
+
inventoryItems: inventoryData?.items ?? [],
|
|
196
|
+
inventoryLocations: inventoryData?.locations ?? [],
|
|
197
|
+
inventoryLoading,
|
|
198
|
+
inventoryError,
|
|
199
|
+
customers: customersData?.customers ?? [],
|
|
200
|
+
customersTotal: customersData?.total ?? 0,
|
|
201
|
+
customersLoading,
|
|
202
|
+
customersError,
|
|
203
|
+
customerSearch,
|
|
204
|
+
setCustomerSearch,
|
|
205
|
+
counts,
|
|
206
|
+
refresh
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
useShopifyDashboard
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=useShopifyDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useShopifyDashboard.ts"],"sourcesContent":["/**\n * useShopifyDashboard — data hook for the Shopify overlay app.\n *\n * Polls all Shopify API endpoints and exposes typed state for each panel.\n * Handles route 404 responses gracefully as \"disconnected\".\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\nexport interface ShopifyStatus {\n connected: boolean;\n shop: {\n name: string;\n domain: string;\n plan: string;\n email: string;\n currencyCode: string;\n } | null;\n}\n\nexport interface ShopifyProduct {\n id: string;\n title: string;\n status: \"ACTIVE\" | \"DRAFT\" | \"ARCHIVED\";\n productType: string;\n vendor: string;\n totalInventory: number;\n priceRange: { min: string; max: string };\n imageUrl: string | null;\n updatedAt: string;\n}\n\nexport interface ShopifyOrder {\n id: string;\n /** e.g. \"#1001\" */\n name: string;\n email: string;\n totalPrice: string;\n currencyCode: string;\n fulfillmentStatus: \"FULFILLED\" | \"UNFULFILLED\" | \"PARTIALLY_FULFILLED\" | null;\n financialStatus: \"PAID\" | \"PENDING\" | \"REFUNDED\" | \"PARTIALLY_REFUNDED\";\n createdAt: string;\n lineItemCount: number;\n}\n\nexport interface ShopifyInventoryItem {\n id: string;\n sku: string;\n productTitle: string;\n variantTitle: string;\n locationId: string | null;\n locationName: string;\n available: number;\n incoming: number;\n}\n\nexport interface ShopifyCustomer {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n ordersCount: number;\n totalSpent: string;\n currencyCode: string;\n createdAt: string;\n}\n\nexport interface ShopifyProductsResponse {\n products: ShopifyProduct[];\n total: number;\n page: number;\n pageSize: number;\n}\n\nexport interface ShopifyOrdersResponse {\n orders: ShopifyOrder[];\n total: number;\n}\n\nexport interface ShopifyInventoryResponse {\n items: ShopifyInventoryItem[];\n locations: string[];\n}\n\nexport interface ShopifyCustomersResponse {\n customers: ShopifyCustomer[];\n total: number;\n}\n\n// ── Aggregated counts for the overview card ───────────────────────────────\n\nexport interface ShopifyAggregateCounts {\n productCount: number;\n orderCount: number;\n customerCount: number;\n}\n\n// ── Hook return shape ─────────────────────────────────────────────────────\n\nexport interface UseShopifyDashboardReturn {\n // Connection\n status: ShopifyStatus | null;\n statusLoading: boolean;\n statusError: string | null;\n\n // Products\n products: ShopifyProduct[];\n productsTotal: number;\n productsPage: number;\n productsLoading: boolean;\n productsError: string | null;\n productSearch: string;\n setProductSearch: (q: string) => void;\n setProductsPage: (page: number) => void;\n\n // Orders\n orders: ShopifyOrder[];\n ordersTotal: number;\n ordersLoading: boolean;\n ordersError: string | null;\n orderStatusFilter: string;\n setOrderStatusFilter: (s: string) => void;\n\n // Inventory\n inventoryItems: ShopifyInventoryItem[];\n inventoryLocations: string[];\n inventoryLoading: boolean;\n inventoryError: string | null;\n\n // Customers\n customers: ShopifyCustomer[];\n customersTotal: number;\n customersLoading: boolean;\n customersError: string | null;\n customerSearch: string;\n setCustomerSearch: (q: string) => void;\n\n // Aggregate counts\n counts: ShopifyAggregateCounts;\n\n // Manual refresh\n refresh: () => void;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────\n\nconst PAGE_SIZE = 20;\nconst POLL_INTERVAL_MS = 30_000;\n\nasync function fetchJson<T>(url: string): Promise<T | null> {\n const res = await fetch(url);\n if (res.status === 404) return null;\n if (!res.ok) {\n const text = await res.text().catch(() => \"Unknown error\");\n throw new Error(`${res.status}: ${text}`);\n }\n return res.json() as Promise<T>;\n}\n\n// ── Hook ──────────────────────────────────────────────────────────────────\n\nexport function useShopifyDashboard(): UseShopifyDashboardReturn {\n // -- Status\n const [status, setStatus] = useState<ShopifyStatus | null>(null);\n const [statusLoading, setStatusLoading] = useState(true);\n const [statusError, setStatusError] = useState<string | null>(null);\n\n // -- Products\n const [productsData, setProductsData] =\n useState<ShopifyProductsResponse | null>(null);\n const [productsLoading, setProductsLoading] = useState(false);\n const [productsError, setProductsError] = useState<string | null>(null);\n const [productsPage, setProductsPage] = useState(1);\n const [productSearch, setProductSearch] = useState(\"\");\n\n // -- Orders\n const [ordersData, setOrdersData] = useState<ShopifyOrdersResponse | null>(\n null,\n );\n const [ordersLoading, setOrdersLoading] = useState(false);\n const [ordersError, setOrdersError] = useState<string | null>(null);\n const [orderStatusFilter, setOrderStatusFilter] = useState(\"any\");\n\n // -- Inventory\n const [inventoryData, setInventoryData] =\n useState<ShopifyInventoryResponse | null>(null);\n const [inventoryLoading, setInventoryLoading] = useState(false);\n const [inventoryError, setInventoryError] = useState<string | null>(null);\n\n // -- Customers\n const [customersData, setCustomersData] =\n useState<ShopifyCustomersResponse | null>(null);\n const [customersLoading, setCustomersLoading] = useState(false);\n const [customersError, setCustomersError] = useState<string | null>(null);\n const [customerSearch, setCustomerSearch] = useState(\"\");\n\n // -- Tick counter to drive polling\n const [tick, setTick] = useState(0);\n const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const refresh = useCallback(() => {\n setTick((n) => n + 1);\n }, []);\n\n // Poll every 30 s when the component is mounted\n useEffect(() => {\n pollRef.current = setInterval(refresh, POLL_INTERVAL_MS);\n return () => {\n if (pollRef.current !== null) clearInterval(pollRef.current);\n };\n }, [refresh]);\n\n // -- Fetch status\n useEffect(() => {\n let cancelled = false;\n setStatusLoading(true);\n setStatusError(null);\n\n fetchJson<ShopifyStatus>(\"/api/shopify/status\")\n .then((data) => {\n if (cancelled) return;\n setStatus(\n data ?? {\n connected: false,\n shop: null,\n },\n );\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setStatusError(\n err instanceof Error ? err.message : \"Failed to load Shopify status.\",\n );\n setStatus({ connected: false, shop: null });\n })\n .finally(() => {\n if (!cancelled) setStatusLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [tick]);\n\n const connected = status?.connected ?? false;\n\n // -- Fetch products (only when connected)\n useEffect(() => {\n if (!connected) return;\n let cancelled = false;\n setProductsLoading(true);\n setProductsError(null);\n\n const params = new URLSearchParams({\n page: String(productsPage),\n limit: String(PAGE_SIZE),\n q: productSearch,\n });\n\n fetchJson<ShopifyProductsResponse>(`/api/shopify/products?${params}`)\n .then((data) => {\n if (cancelled) return;\n setProductsData(\n data ?? {\n products: [],\n total: 0,\n page: productsPage,\n pageSize: PAGE_SIZE,\n },\n );\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setProductsError(\n err instanceof Error ? err.message : \"Failed to load products.\",\n );\n })\n .finally(() => {\n if (!cancelled) setProductsLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [connected, productsPage, productSearch, tick]);\n\n // -- Fetch orders\n useEffect(() => {\n if (!connected) return;\n let cancelled = false;\n setOrdersLoading(true);\n setOrdersError(null);\n\n const params = new URLSearchParams({\n status: orderStatusFilter,\n limit: String(PAGE_SIZE),\n });\n\n fetchJson<ShopifyOrdersResponse>(`/api/shopify/orders?${params}`)\n .then((data) => {\n if (cancelled) return;\n setOrdersData(data ?? { orders: [], total: 0 });\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setOrdersError(\n err instanceof Error ? err.message : \"Failed to load orders.\",\n );\n })\n .finally(() => {\n if (!cancelled) setOrdersLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [connected, orderStatusFilter, tick]);\n\n // -- Fetch inventory\n useEffect(() => {\n if (!connected) return;\n let cancelled = false;\n setInventoryLoading(true);\n setInventoryError(null);\n\n fetchJson<ShopifyInventoryResponse>(\"/api/shopify/inventory\")\n .then((data) => {\n if (cancelled) return;\n setInventoryData(data ?? { items: [], locations: [] });\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setInventoryError(\n err instanceof Error ? err.message : \"Failed to load inventory.\",\n );\n })\n .finally(() => {\n if (!cancelled) setInventoryLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [connected, tick]);\n\n // -- Fetch customers\n useEffect(() => {\n if (!connected) return;\n let cancelled = false;\n setCustomersLoading(true);\n setCustomersError(null);\n\n const params = new URLSearchParams({\n q: customerSearch,\n limit: String(PAGE_SIZE),\n });\n\n fetchJson<ShopifyCustomersResponse>(`/api/shopify/customers?${params}`)\n .then((data) => {\n if (cancelled) return;\n setCustomersData(data ?? { customers: [], total: 0 });\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setCustomersError(\n err instanceof Error ? err.message : \"Failed to load customers.\",\n );\n })\n .finally(() => {\n if (!cancelled) setCustomersLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [connected, customerSearch, tick]);\n\n const counts: ShopifyAggregateCounts = {\n productCount: productsData?.total ?? 0,\n orderCount: ordersData?.total ?? 0,\n customerCount: customersData?.total ?? 0,\n };\n\n return {\n status,\n statusLoading,\n statusError,\n\n products: productsData?.products ?? [],\n productsTotal: productsData?.total ?? 0,\n productsPage,\n productsLoading,\n productsError,\n productSearch,\n setProductSearch,\n setProductsPage,\n\n orders: ordersData?.orders ?? [],\n ordersTotal: ordersData?.total ?? 0,\n ordersLoading,\n ordersError,\n orderStatusFilter,\n setOrderStatusFilter,\n\n inventoryItems: inventoryData?.items ?? [],\n inventoryLocations: inventoryData?.locations ?? [],\n inventoryLoading,\n inventoryError,\n\n customers: customersData?.customers ?? [],\n customersTotal: customersData?.total ?? 0,\n customersLoading,\n customersError,\n customerSearch,\n setCustomerSearch,\n\n counts,\n refresh,\n };\n}\n"],"mappings":"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","names":[]}
|