@burdenoff/microfe-devportal 2026.1006.1 → 2026.1013.2
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/devportal/DevportalRoutes.js.map +1 -1
- package/dist/devportal/components/FeatureFlaggedRoute.js.map +1 -1
- package/dist/devportal/components/FormField.d.ts.map +1 -1
- package/dist/devportal/components/FormField.js +1 -1
- package/dist/devportal/components/FormField.js.map +1 -1
- package/dist/devportal/pages/AnalyticsDashboardPage.js +2 -2
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +1 -1
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +1 -1
- package/dist/devportal/pages/AppLogsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +2 -2
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/RevenueDashboardPage.js +1 -1
- package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.js +1 -1
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/pages/SellerOrdersPage.js +2 -2
- package/dist/devportal/pages/SellerOrdersPage.js.map +1 -1
- package/dist/devportal/pages/WebhooksPage.js +1 -1
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerOrdersPage.js","names":[],"sources":["../../../src/devportal/pages/SellerOrdersPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo, useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n ShoppingBag,\n Loader2,\n AlertCircle,\n Package,\n ChevronDown,\n Flag,\n CheckCircle2,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard, ReportOrderIssueModal } from \"../components\";\nimport type { OrderIssueCategory } from \"../components/ReportOrderIssueModal\";\nimport { formatCurrency } from \"../utils/currency\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n kind: \"DIGITAL\" | \"PHYSICAL\";\n}\n\ninterface SellerOrder {\n id: string;\n storeOrderId: string | null;\n storeProductId: string | null;\n grossAmount: number;\n netAmount: number;\n platformFee: number;\n currency: string;\n status: SaleStatus;\n buyerUserId: string;\n purchasedAt: string;\n refundAmount: number | null;\n refundedAt: string | null;\n refundReason: string | null;\n discountAmount: number | null;\n taxAmount: number | null;\n}\n\ninterface IssueFlag {\n id: string;\n category: OrderIssueCategory;\n note: string | null;\n status: \"OPEN\" | \"RESOLVED\";\n raisedByOrgId: string;\n createdAt: string;\n}\n\nconst ISSUE_CATEGORY_LABELS: Record<OrderIssueCategory, string> = {\n OUT_OF_STOCK: \"Out of stock\",\n CANNOT_FULFILL: \"Cannot fulfill\",\n WRONG_ADDRESS: \"Wrong address\",\n OTHER: \"Other\",\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_STYLES: Record<SaleStatus, { label: string; className: string }> =\n {\n PENDING: {\n label: \"Pending\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n COMPLETED: {\n label: \"Completed\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n REFUNDED: {\n label: \"Refunded\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n PARTIAL_REFUND: {\n label: \"Partial Refund\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DISPUTED: {\n label: \"Disputed\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CHARGEBACK: {\n label: \"Chargeback\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CANCELLED: {\n label: \"Cancelled\",\n className: \"text-text-muted bg-bg-muted\",\n },\n };\n\nconst STATUS_FILTER_OPTIONS: { value: SaleStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Orders\" },\n { value: \"PENDING\", label: \"Pending\" },\n { value: \"COMPLETED\", label: \"Completed\" },\n { value: \"REFUNDED\", label: \"Refunded\" },\n { value: \"PARTIAL_REFUND\", label: \"Partial Refund\" },\n { value: \"DISPUTED\", label: \"Disputed\" },\n { value: \"CANCELLED\", label: \"Cancelled\" },\n];\n\n// ---------------------------------------------------------------------------\n// GraphQL\n// ---------------------------------------------------------------------------\n\nconst ORGS_QUERY = `\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n kind\n }\n }\n`;\n\nconst ORDERS_QUERY = `\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n`;\n\nconst RESOLVE_ORDER_ISSUE_MUTATION = `\n mutation ResolveOrderIssue($id: ID!, $organizationId: ID) {\n resolveOrderIssueFlag(id: $id, organizationId: $organizationId) {\n id\n status\n }\n }\n`;\n\nconst ISSUE_FLAG_FIELDS = `id category note status raisedByOrgId createdAt`;\n\n/**\n * One `orderIssueFlags` call per visible physical order, batched into a\n * single request via GraphQL field aliasing — avoids N network round-trips\n * for a page of up to 25 orders.\n */\nfunction buildIssueFlagsBatchQuery(orderIds: string[]): string {\n const fields = orderIds\n .map(\n (id, i) =>\n `f${i}: orderIssueFlags(orderId: ${JSON.stringify(id)}) { ${ISSUE_FLAG_FIELDS} }`,\n )\n .join(\"\\n \");\n return `query SellerOrderIssueFlags {\\n ${fields}\\n }`;\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nfunction shortOrderId(id: string | null, fallback: string): string {\n if (id) return `#${id.slice(0, 8).toUpperCase()}`;\n return `#${fallback.slice(0, 8).toUpperCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport const SellerOrdersPage: FC = () => {\n const { apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [orgsLoading, setOrgsLoading] = useState(true);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n\n const [ordersLoading, setOrdersLoading] = useState(false);\n const [orders, setOrders] = useState<SellerOrder[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [statusFilter, setStatusFilter] = useState<SaleStatus | \"\">(\"\");\n const [page, setPage] = useState(0);\n const PAGE_SIZE = 25;\n\n const headers = useMemo(\n () => ({\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n }),\n [authToken],\n );\n\n // Fetch orgs on mount\n useEffect(() => {\n let cancelled = false;\n setOrgsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORGS_QUERY }),\n });\n const json = (await res.json()) as {\n data?: { myDeveloperOrganizations?: OrgSummary[] };\n };\n if (cancelled) return;\n // Seller portal only ever shows PHYSICAL-kind orgs — see\n // DeveloperOrgKind (BOFF-7285).\n const fetched = (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n );\n setOrgs(fetched);\n if (fetched.length > 0) setSelectedOrgId(fetched[0]!.id);\n } catch {\n // leave orgs empty\n } finally {\n if (!cancelled) setOrgsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, headers]);\n\n // Fetch orders when org/filter/page changes\n useEffect(() => {\n if (!selectedOrgId) return;\n let cancelled = false;\n setOrdersLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const variables: Record<string, unknown> = {\n organizationId: selectedOrgId,\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n };\n if (statusFilter) variables.status = statusFilter;\n\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORDERS_QUERY, variables }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: {\n developerOrgSales?: {\n totalCount: number;\n edges: {\n node: Omit<SellerOrder, \"status\"> & { status: string };\n }[];\n };\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"GraphQL error\");\n }\n const conn = json.data?.developerOrgSales;\n setTotalCount(conn?.totalCount ?? 0);\n // Show all orders; physical-product ones have storeProductId set\n setOrders(\n (conn?.edges ?? []).map((e) => ({\n ...e.node,\n status: e.node.status as SaleStatus,\n })),\n );\n } catch (err) {\n if (!cancelled)\n setError(\n err instanceof Error ? err.message : \"Failed to load orders\",\n );\n } finally {\n if (!cancelled) setOrdersLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [selectedOrgId, statusFilter, page, gqlUrl, headers, retryToken]);\n\n // Physical product orders (storeProductId set) vs digital\n const physicalOrders = useMemo(\n () => orders.filter((o) => o.storeProductId !== null),\n [orders],\n );\n\n // Seller-reported issue flags for the currently visible physical orders,\n // keyed by storeOrderId. Fetched as one batched (aliased) request.\n const [issueFlagsByOrderId, setIssueFlagsByOrderId] = useState<\n Record<string, IssueFlag[]>\n >({});\n const [reportModalOrderId, setReportModalOrderId] = useState<string | null>(\n null,\n );\n const [resolvingFlagId, setResolvingFlagId] = useState<string | null>(null);\n\n const visibleStoreOrderIds = useMemo(\n () =>\n physicalOrders\n .map((o) => o.storeOrderId)\n .filter((id): id is string => id !== null),\n [physicalOrders],\n );\n\n const fetchIssueFlags = useCallback(async () => {\n if (visibleStoreOrderIds.length === 0) {\n setIssueFlagsByOrderId({});\n return;\n }\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n query: buildIssueFlagsBatchQuery(visibleStoreOrderIds),\n }),\n });\n const json = (await res.json()) as {\n data?: Record<string, IssueFlag[]>;\n };\n const next: Record<string, IssueFlag[]> = {};\n visibleStoreOrderIds.forEach((id, i) => {\n next[id] = json.data?.[`f${i}`] ?? [];\n });\n setIssueFlagsByOrderId(next);\n } catch {\n // Non-critical — the orders table itself still renders fine without flags\n }\n }, [gqlUrl, headers, visibleStoreOrderIds]);\n\n useEffect(() => {\n void fetchIssueFlags();\n }, [fetchIssueFlags]);\n\n const handleResolveFlag = async (flagId: string, organizationId: string) => {\n setResolvingFlagId(flagId);\n try {\n await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n query: RESOLVE_ORDER_ISSUE_MUTATION,\n variables: { id: flagId, organizationId },\n }),\n });\n await fetchIssueFlags();\n } finally {\n setResolvingFlagId(null);\n }\n };\n\n const stats = useMemo(() => {\n const all = physicalOrders;\n return {\n total: totalCount,\n pending: all.filter((o) => o.status === \"PENDING\").length,\n completed: all.filter((o) => o.status === \"COMPLETED\").length,\n refunded: all.filter(\n (o) => o.status === \"REFUNDED\" || o.status === \"PARTIAL_REFUND\",\n ).length,\n };\n }, [physicalOrders, totalCount]);\n\n if (orgsLoading) {\n return (\n <PageLayout title={tr(\"devportal.orders\", \"Orders\")}>\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (orgs.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<ShoppingBag size={32} />}\n title={tr(\"devportal.noOrgForOrders\", \"No organization yet\")}\n description={tr(\n \"devportal.createOrgToSell\",\n \"Create a developer organization to start selling physical products and receive orders.\",\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n {/* Controls row */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-6\">\n {/* Org picker (shown only when multiple orgs) */}\n {orgs.length > 1 && (\n <div className=\"relative\">\n <select\n value={selectedOrgId ?? \"\"}\n onChange={(e) => {\n setSelectedOrgId(e.target.value);\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {orgs.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n )}\n\n {/* Status filter */}\n <div className=\"relative\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as SaleStatus | \"\");\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_FILTER_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n </div>\n\n {/* Stats row */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4 mb-6\">\n {[\n { label: \"Total Orders\", value: stats.total },\n {\n label: \"Pending\",\n value: stats.pending,\n color: \"text-status-warning-text\",\n },\n {\n label: \"Completed\",\n value: stats.completed,\n color: \"text-status-success-text\",\n },\n {\n label: \"Refunded\",\n value: stats.refunded,\n color: \"text-status-error-text\",\n },\n ].map((s) => (\n <div\n key={s.label}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-4 py-3\"\n >\n <p className={`text-xs ${s.color ?? \"text-text-secondary\"}`}>\n {s.label}\n </p>\n <p className=\"mt-1 text-xl font-semibold tabular-nums\">\n {ordersLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n s.value\n )}\n </p>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"mb-4 flex items-center gap-3 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle\n size={16}\n className=\"shrink-0 text-status-error-text\"\n />\n <span className=\"text-status-error-text\">{error}</span>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"ml-auto text-xs font-medium text-status-error-text underline\"\n >\n Retry\n </button>\n </div>\n )}\n\n {/* Orders table */}\n {ordersLoading ? (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : physicalOrders.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={\n statusFilter\n ? tr(\n \"devportal.noOrdersForFilter\",\n \"No orders match this filter\",\n )\n : tr(\"devportal.noOrdersYet\", \"No orders yet\")\n }\n description={\n statusFilter\n ? tr(\n \"devportal.tryDifferentFilter\",\n \"Try selecting a different status filter\",\n )\n : tr(\n \"devportal.ordersWillAppear\",\n \"Orders placed for your physical items will appear here once customers make purchases.\",\n )\n }\n />\n ) : (\n <>\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Order\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Product ID\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Buyer\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Gross\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Your Earnings\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Date\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Fulfillment Issue\n </th>\n </tr>\n </thead>\n <tbody>\n {physicalOrders.map((order) => {\n const badge =\n STATUS_STYLES[order.status] ?? STATUS_STYLES.PENDING;\n const flags = order.storeOrderId\n ? (issueFlagsByOrderId[order.storeOrderId] ?? [])\n : [];\n const openFlag = flags.find((f) => f.status === \"OPEN\");\n return (\n <tr\n key={order.id}\n className=\"border-b border-border-subtle last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs font-semibold text-text-primary\">\n {shortOrderId(order.storeOrderId, order.id)}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {order.storeProductId\n ? `…${order.storeProductId.slice(-8)}`\n : \"—\"}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {`…${order.buyerUserId.slice(-8)}`}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-primary\">\n {formatCurrency(order.grossAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-status-success-text\">\n {formatCurrency(order.netAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n {order.refundAmount != null &&\n order.refundAmount > 0 && (\n <p className=\"mt-0.5 text-[11px] text-status-error-text\">\n Refunded{\" \"}\n {formatCurrency(\n order.refundAmount,\n order.currency,\n )}\n </p>\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary whitespace-nowrap\">\n {formatDate(order.purchasedAt)}\n </td>\n <td className=\"px-4 py-3\">\n {!order.storeOrderId ? (\n <span className=\"text-xs text-text-muted\">—</span>\n ) : openFlag ? (\n <div className=\"flex items-center gap-2\">\n <span\n title={openFlag.note ?? undefined}\n className=\"flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <Flag size={11} />\n {ISSUE_CATEGORY_LABELS[openFlag.category]}\n </span>\n {openFlag.raisedByOrgId === selectedOrgId && (\n <button\n onClick={() =>\n void handleResolveFlag(\n openFlag.id,\n openFlag.raisedByOrgId,\n )\n }\n disabled={resolvingFlagId === openFlag.id}\n className=\"text-xs font-medium text-text-secondary underline hover:text-text-primary disabled:opacity-50\"\n >\n {resolvingFlagId === openFlag.id ? (\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n ) : (\n \"Mark Resolved\"\n )}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex items-center gap-2\">\n {flags.length > 0 && (\n <span\n title=\"Previously reported and resolved\"\n className=\"flex items-center gap-1 text-xs text-status-success-text\"\n >\n <CheckCircle2 size={11} />\n Resolved\n </span>\n )}\n <button\n onClick={() =>\n setReportModalOrderId(order.storeOrderId)\n }\n className=\"text-xs font-medium text-text-secondary underline hover:text-text-primary\"\n >\n Report Issue\n </button>\n </div>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n {totalCount > PAGE_SIZE && (\n <div className=\"mt-4 flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * PAGE_SIZE + 1}–\n {Math.min((page + 1) * PAGE_SIZE, totalCount)} of {totalCount}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n disabled={page === 0}\n onClick={() => setPage((p) => p - 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <button\n disabled={(page + 1) * PAGE_SIZE >= totalCount}\n onClick={() => setPage((p) => p + 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </PageLayout>\n\n {selectedOrgId && (\n <ReportOrderIssueModal\n open={reportModalOrderId !== null}\n onOpenChange={(open) => {\n if (!open) setReportModalOrderId(null);\n }}\n orderId={reportModalOrderId ?? \"\"}\n organizationId={selectedOrgId}\n gqlUrl={gqlUrl}\n headers={headers}\n onReported={() => void fetchIssueFlags()}\n />\n )}\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerOrdersPage.js","names":[],"sources":["../../../src/devportal/pages/SellerOrdersPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo, useCallback } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n ShoppingBag,\n Loader2,\n AlertCircle,\n Package,\n ChevronDown,\n Flag,\n CheckCircle2,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard, ReportOrderIssueModal } from \"../components\";\nimport type { OrderIssueCategory } from \"../components/ReportOrderIssueModal\";\nimport { formatCurrency } from \"../utils/currency\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n kind: \"DIGITAL\" | \"PHYSICAL\";\n}\n\ninterface SellerOrder {\n id: string;\n storeOrderId: string | null;\n storeProductId: string | null;\n grossAmount: number;\n netAmount: number;\n platformFee: number;\n currency: string;\n status: SaleStatus;\n buyerUserId: string;\n purchasedAt: string;\n refundAmount: number | null;\n refundedAt: string | null;\n refundReason: string | null;\n discountAmount: number | null;\n taxAmount: number | null;\n}\n\ninterface IssueFlag {\n id: string;\n category: OrderIssueCategory;\n note: string | null;\n status: \"OPEN\" | \"RESOLVED\";\n raisedByOrgId: string;\n createdAt: string;\n}\n\nconst ISSUE_CATEGORY_LABELS: Record<OrderIssueCategory, string> = {\n OUT_OF_STOCK: \"Out of stock\",\n CANNOT_FULFILL: \"Cannot fulfill\",\n WRONG_ADDRESS: \"Wrong address\",\n OTHER: \"Other\",\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_STYLES: Record<SaleStatus, { label: string; className: string }> =\n {\n PENDING: {\n label: \"Pending\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n COMPLETED: {\n label: \"Completed\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n REFUNDED: {\n label: \"Refunded\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n PARTIAL_REFUND: {\n label: \"Partial Refund\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DISPUTED: {\n label: \"Disputed\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CHARGEBACK: {\n label: \"Chargeback\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CANCELLED: {\n label: \"Cancelled\",\n className: \"text-text-muted bg-bg-muted\",\n },\n };\n\nconst STATUS_FILTER_OPTIONS: { value: SaleStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Orders\" },\n { value: \"PENDING\", label: \"Pending\" },\n { value: \"COMPLETED\", label: \"Completed\" },\n { value: \"REFUNDED\", label: \"Refunded\" },\n { value: \"PARTIAL_REFUND\", label: \"Partial Refund\" },\n { value: \"DISPUTED\", label: \"Disputed\" },\n { value: \"CANCELLED\", label: \"Cancelled\" },\n];\n\n// ---------------------------------------------------------------------------\n// GraphQL\n// ---------------------------------------------------------------------------\n\nconst ORGS_QUERY = `\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n kind\n }\n }\n`;\n\nconst ORDERS_QUERY = `\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n`;\n\nconst RESOLVE_ORDER_ISSUE_MUTATION = `\n mutation ResolveOrderIssue($id: ID!, $organizationId: ID) {\n resolveOrderIssueFlag(id: $id, organizationId: $organizationId) {\n id\n status\n }\n }\n`;\n\nconst ISSUE_FLAG_FIELDS = `id category note status raisedByOrgId createdAt`;\n\n/**\n * One `orderIssueFlags` call per visible physical order, batched into a\n * single request via GraphQL field aliasing — avoids N network round-trips\n * for a page of up to 25 orders.\n */\nfunction buildIssueFlagsBatchQuery(orderIds: string[]): string {\n const fields = orderIds\n .map(\n (id, i) =>\n `f${i}: orderIssueFlags(orderId: ${JSON.stringify(id)}) { ${ISSUE_FLAG_FIELDS} }`,\n )\n .join(\"\\n \");\n return `query SellerOrderIssueFlags {\\n ${fields}\\n }`;\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nfunction shortOrderId(id: string | null, fallback: string): string {\n if (id) return `#${id.slice(0, 8).toUpperCase()}`;\n return `#${fallback.slice(0, 8).toUpperCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport const SellerOrdersPage: FC = () => {\n const { apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [orgsLoading, setOrgsLoading] = useState(true);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n\n const [ordersLoading, setOrdersLoading] = useState(false);\n const [orders, setOrders] = useState<SellerOrder[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [statusFilter, setStatusFilter] = useState<SaleStatus | \"\">(\"\");\n const [page, setPage] = useState(0);\n const PAGE_SIZE = 25;\n\n const headers = useMemo(\n () => ({\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n }),\n [authToken],\n );\n\n // Fetch orgs on mount\n useEffect(() => {\n let cancelled = false;\n setOrgsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORGS_QUERY }),\n });\n const json = (await res.json()) as {\n data?: { myDeveloperOrganizations?: OrgSummary[] };\n };\n if (cancelled) return;\n // Seller portal only ever shows PHYSICAL-kind orgs — see\n // DeveloperOrgKind (BOFF-7285).\n const fetched = (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n );\n setOrgs(fetched);\n if (fetched.length > 0) setSelectedOrgId(fetched[0]!.id);\n } catch {\n // leave orgs empty\n } finally {\n if (!cancelled) setOrgsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, headers]);\n\n // Fetch orders when org/filter/page changes\n useEffect(() => {\n if (!selectedOrgId) return;\n let cancelled = false;\n setOrdersLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const variables: Record<string, unknown> = {\n organizationId: selectedOrgId,\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n };\n if (statusFilter) variables.status = statusFilter;\n\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORDERS_QUERY, variables }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: {\n developerOrgSales?: {\n totalCount: number;\n edges: {\n node: Omit<SellerOrder, \"status\"> & { status: string };\n }[];\n };\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"GraphQL error\");\n }\n const conn = json.data?.developerOrgSales;\n setTotalCount(conn?.totalCount ?? 0);\n // Show all orders; physical-product ones have storeProductId set\n setOrders(\n (conn?.edges ?? []).map((e) => ({\n ...e.node,\n status: e.node.status as SaleStatus,\n })),\n );\n } catch (err) {\n if (!cancelled)\n setError(\n err instanceof Error ? err.message : \"Failed to load orders\",\n );\n } finally {\n if (!cancelled) setOrdersLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [selectedOrgId, statusFilter, page, gqlUrl, headers, retryToken]);\n\n // Physical product orders (storeProductId set) vs digital\n const physicalOrders = useMemo(\n () => orders.filter((o) => o.storeProductId !== null),\n [orders],\n );\n\n // Seller-reported issue flags for the currently visible physical orders,\n // keyed by storeOrderId. Fetched as one batched (aliased) request.\n const [issueFlagsByOrderId, setIssueFlagsByOrderId] = useState<\n Record<string, IssueFlag[]>\n >({});\n const [reportModalOrderId, setReportModalOrderId] = useState<string | null>(\n null,\n );\n const [resolvingFlagId, setResolvingFlagId] = useState<string | null>(null);\n\n const visibleStoreOrderIds = useMemo(\n () =>\n physicalOrders\n .map((o) => o.storeOrderId)\n .filter((id): id is string => id !== null),\n [physicalOrders],\n );\n\n const fetchIssueFlags = useCallback(async () => {\n if (visibleStoreOrderIds.length === 0) {\n setIssueFlagsByOrderId({});\n return;\n }\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n query: buildIssueFlagsBatchQuery(visibleStoreOrderIds),\n }),\n });\n const json = (await res.json()) as {\n data?: Record<string, IssueFlag[]>;\n };\n const next: Record<string, IssueFlag[]> = {};\n visibleStoreOrderIds.forEach((id, i) => {\n next[id] = json.data?.[`f${i}`] ?? [];\n });\n setIssueFlagsByOrderId(next);\n } catch {\n // Non-critical — the orders table itself still renders fine without flags\n }\n }, [gqlUrl, headers, visibleStoreOrderIds]);\n\n useEffect(() => {\n void fetchIssueFlags();\n }, [fetchIssueFlags]);\n\n const handleResolveFlag = async (flagId: string, organizationId: string) => {\n setResolvingFlagId(flagId);\n try {\n await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n query: RESOLVE_ORDER_ISSUE_MUTATION,\n variables: { id: flagId, organizationId },\n }),\n });\n await fetchIssueFlags();\n } finally {\n setResolvingFlagId(null);\n }\n };\n\n const stats = useMemo(() => {\n const all = physicalOrders;\n return {\n total: totalCount,\n pending: all.filter((o) => o.status === \"PENDING\").length,\n completed: all.filter((o) => o.status === \"COMPLETED\").length,\n refunded: all.filter(\n (o) => o.status === \"REFUNDED\" || o.status === \"PARTIAL_REFUND\",\n ).length,\n };\n }, [physicalOrders, totalCount]);\n\n if (orgsLoading) {\n return (\n <PageLayout title={tr(\"devportal.orders\", \"Orders\")}>\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (orgs.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<ShoppingBag size={32} />}\n title={tr(\"devportal.noOrgForOrders\", \"No organization yet\")}\n description={tr(\n \"devportal.createOrgToSell\",\n \"Create a developer organization to start selling physical products and receive orders.\",\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n {/* Controls row */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-6\">\n {/* Org picker (shown only when multiple orgs) */}\n {orgs.length > 1 && (\n <div className=\"relative\">\n <select\n value={selectedOrgId ?? \"\"}\n onChange={(e) => {\n setSelectedOrgId(e.target.value);\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {orgs.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n )}\n\n {/* Status filter */}\n <div className=\"relative\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as SaleStatus | \"\");\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {STATUS_FILTER_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n </div>\n\n {/* Stats row */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4 mb-6\">\n {[\n { label: \"Total Orders\", value: stats.total },\n {\n label: \"Pending\",\n value: stats.pending,\n color: \"text-status-warning-text\",\n },\n {\n label: \"Completed\",\n value: stats.completed,\n color: \"text-status-success-text\",\n },\n {\n label: \"Refunded\",\n value: stats.refunded,\n color: \"text-status-error-text\",\n },\n ].map((s) => (\n <div\n key={s.label}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-4 py-3\"\n >\n <p className={`text-xs ${s.color ?? \"text-text-secondary\"}`}>\n {s.label}\n </p>\n <p className=\"mt-1 text-xl font-semibold tabular-nums\">\n {ordersLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n s.value\n )}\n </p>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"mb-4 flex items-center gap-3 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle\n size={16}\n className=\"shrink-0 text-status-error-text\"\n />\n <span className=\"text-status-error-text\">{error}</span>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"ml-auto text-xs font-medium text-status-error-text underline\"\n >\n Retry\n </button>\n </div>\n )}\n\n {/* Orders table */}\n {ordersLoading ? (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : physicalOrders.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={\n statusFilter\n ? tr(\n \"devportal.noOrdersForFilter\",\n \"No orders match this filter\",\n )\n : tr(\"devportal.noOrdersYet\", \"No orders yet\")\n }\n description={\n statusFilter\n ? tr(\n \"devportal.tryDifferentFilter\",\n \"Try selecting a different status filter\",\n )\n : tr(\n \"devportal.ordersWillAppear\",\n \"Orders placed for your physical items will appear here once customers make purchases.\",\n )\n }\n />\n ) : (\n <>\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Order\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Product ID\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Buyer\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Gross\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Your Earnings\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Date\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Fulfillment Issue\n </th>\n </tr>\n </thead>\n <tbody>\n {physicalOrders.map((order) => {\n const badge =\n STATUS_STYLES[order.status] ?? STATUS_STYLES.PENDING;\n const flags = order.storeOrderId\n ? (issueFlagsByOrderId[order.storeOrderId] ?? [])\n : [];\n const openFlag = flags.find((f) => f.status === \"OPEN\");\n return (\n <tr\n key={order.id}\n className=\"border-b border-border-subtle last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs font-semibold text-text-primary\">\n {shortOrderId(order.storeOrderId, order.id)}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {order.storeProductId\n ? `…${order.storeProductId.slice(-8)}`\n : \"—\"}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {`…${order.buyerUserId.slice(-8)}`}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-primary\">\n {formatCurrency(order.grossAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-status-success-text\">\n {formatCurrency(order.netAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n {order.refundAmount != null &&\n order.refundAmount > 0 && (\n <p className=\"mt-0.5 text-[11px] text-status-error-text\">\n Refunded{\" \"}\n {formatCurrency(\n order.refundAmount,\n order.currency,\n )}\n </p>\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary whitespace-nowrap\">\n {formatDate(order.purchasedAt)}\n </td>\n <td className=\"px-4 py-3\">\n {!order.storeOrderId ? (\n <span className=\"text-xs text-text-muted\">—</span>\n ) : openFlag ? (\n <div className=\"flex items-center gap-2\">\n <span\n title={openFlag.note ?? undefined}\n className=\"flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <Flag size={11} />\n {ISSUE_CATEGORY_LABELS[openFlag.category]}\n </span>\n {openFlag.raisedByOrgId === selectedOrgId && (\n <button\n onClick={() =>\n void handleResolveFlag(\n openFlag.id,\n openFlag.raisedByOrgId,\n )\n }\n disabled={resolvingFlagId === openFlag.id}\n className=\"text-xs font-medium text-text-secondary underline hover:text-text-primary disabled:opacity-50\"\n >\n {resolvingFlagId === openFlag.id ? (\n <Loader2 className=\"h-3 w-3 animate-spin\" />\n ) : (\n \"Mark Resolved\"\n )}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex items-center gap-2\">\n {flags.length > 0 && (\n <span\n title=\"Previously reported and resolved\"\n className=\"flex items-center gap-1 text-xs text-status-success-text\"\n >\n <CheckCircle2 size={11} />\n Resolved\n </span>\n )}\n <button\n onClick={() =>\n setReportModalOrderId(order.storeOrderId)\n }\n className=\"text-xs font-medium text-text-secondary underline hover:text-text-primary\"\n >\n Report Issue\n </button>\n </div>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n {totalCount > PAGE_SIZE && (\n <div className=\"mt-4 flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * PAGE_SIZE + 1}–\n {Math.min((page + 1) * PAGE_SIZE, totalCount)} of {totalCount}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n disabled={page === 0}\n onClick={() => setPage((p) => p - 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <button\n disabled={(page + 1) * PAGE_SIZE >= totalCount}\n onClick={() => setPage((p) => p + 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </PageLayout>\n\n {selectedOrgId && (\n <ReportOrderIssueModal\n open={reportModalOrderId !== null}\n onOpenChange={(open) => {\n if (!open) setReportModalOrderId(null);\n }}\n orderId={reportModalOrderId ?? \"\"}\n organizationId={selectedOrgId}\n gqlUrl={gqlUrl}\n headers={headers}\n onReported={() => void fetchIssueFlags()}\n />\n )}\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -355,7 +355,7 @@ var A = () => {
|
|
|
355
355
|
}), /* @__PURE__ */ T("select", {
|
|
356
356
|
value: R,
|
|
357
357
|
onChange: (e) => z(e.target.value),
|
|
358
|
-
className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
|
|
358
|
+
className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
359
359
|
children: [
|
|
360
360
|
/* @__PURE__ */ w("option", {
|
|
361
361
|
value: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebhooksPage.js","names":[],"sources":["../../../src/devportal/pages/WebhooksPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Webhook,\n CheckCircle,\n XCircle,\n PauseCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n AppWindow,\n} from \"lucide-react\";\nimport { useParams } from \"react-router\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { PermissionGuard } from \"../components\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type {\n Application,\n Webhook as WebhookType,\n WebhookStatus,\n} from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useGetApplicationsQuery,\n useGetApplicationQuery,\n useGetWebhooksQuery,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type DevportalWebhookFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Map GraphQL webhook to local Webhook type\n */\nfunction mapWebhook(webhook: DevportalWebhookFieldsFragment): WebhookType {\n return {\n id: webhook.id,\n applicationId: webhook.applicationId,\n name: webhook.name,\n description: webhook.description ?? undefined,\n url: webhook.url,\n secret: undefined,\n events: webhook.events ?? [],\n isActive: webhook.isActive,\n status: webhook.status as WebhookStatus,\n maxRetries: webhook.maxRetries ?? 3,\n retryDelay: webhook.retryDelay ?? 60,\n retryEnabled: webhook.retryEnabled ?? true,\n lastTriggeredAt: webhook.lastTriggeredAt ?? undefined,\n lastSuccessAt: webhook.lastSuccessAt ?? undefined,\n lastFailureAt: webhook.lastFailureAt ?? undefined,\n lastFailureReason: webhook.lastFailureReason ?? undefined,\n failureCount: webhook.failureCount ?? 0,\n successCount: webhook.successCount ?? 0,\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n };\n}\n\n/**\n * Webhooks listing page\n *\n * Displays webhooks for a specific application.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const WebhooksPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const createPerm = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<WebhookStatus | \"\">(\"\");\n\n // Fetch app name for breadcrumb when viewing webhooks for a specific app\n const { data: singleAppData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = singleAppData?.application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, singleAppData?.application?.name]);\n\n const {\n data: appsData,\n loading: appsLoading,\n error: appsError,\n refetch: refetchApps,\n } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch webhooks from GraphQL API (only if we have an applicationId)\n const { data, loading, error, refetch } = useGetWebhooksQuery({\n variables: {\n applicationId: appId || \"\",\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const webhooks = data?.webhooks?.map(mapWebhook) ?? [];\n const allApplications: Application[] =\n appsData?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node as Application,\n ) ?? [];\n // Scope app picker to active org\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter webhooks\n const filteredWebhooks = webhooks.filter((webhook: WebhookType) => {\n const matchesSearch =\n !searchQuery ||\n webhook.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n webhook.url.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || webhook.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n\n const getStatusIcon = (status: WebhookStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return <CheckCircle size={16} className=\"text-status-success-text\" />;\n case \"PAUSED\":\n return <PauseCircle size={16} className=\"text-status-warning-text\" />;\n case \"FAILED\":\n return <XCircle size={16} className=\"text-status-error-text\" />;\n case \"DISABLED\":\n return <AlertTriangle size={16} className=\"text-text-tertiary\" />;\n default:\n return null;\n }\n };\n\n const calculateSuccessRate = (webhook: WebhookType): number => {\n const total = webhook.successCount + webhook.failureCount;\n if (total === 0) return 100;\n return Math.round((webhook.successCount / total) * 100);\n };\n\n // If no applicationId, show prompt to select an application\n if (!appId) {\n const filteredApplications = applications.filter((application) => {\n if (!searchQuery) return true;\n\n const normalizedQuery = searchQuery.toLowerCase();\n return (\n application.name.toLowerCase().includes(normalizedQuery) ||\n (application.slug ?? \"\").toLowerCase().includes(normalizedQuery)\n );\n });\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApps\",\n \"Manage webhooks for your applications\",\n )}\n purpose={tr(\n \"devportal.webhooksPickerPurpose\",\n \"Webhooks push real-time events (installs, payments, app updates) to a URL you host, so your backend can react without polling. Pick an application to view, add, or test the webhook endpoints it delivers to.\",\n )}\n >\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.webhooksSelectAppHint\",\n \"Select an application below to view and manage its webhooks.\",\n )}\n </p>\n <div className=\"max-w-md\">\n <div className=\"relative\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search applications...\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n </div>\n\n {appsLoading ? (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : appsError ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n Failed to load applications\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(appsError)}\n </p>\n <button\n onClick={() => refetchApps()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n Try Again\n </button>\n </div>\n ) : filteredApplications.length > 0 ? (\n <div className=\"space-y-2\">\n {filteredApplications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() =>\n handleNavigate(`/apps/${application.id}/webhooks`)\n }\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n icon={<AppWindow size={32} />}\n title=\"No applications available\"\n description={\n searchQuery\n ? \"No applications matched your search.\"\n : \"Create an application before configuring webhooks.\"\n }\n action={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n Create Application\n </button>\n }\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingWebhooks\", \"Loading webhooks...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadWebhooks\", \"Failed to load webhooks\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"name\",\n header: \"Webhook\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n webhook.status === \"ACTIVE\"\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : webhook.status === \"FAILED\"\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : webhook.status === \"PAUSED\"\n ? \"bg-status-warning-bg-subtle text-status-warning-text\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Webhook size={20} />\n </div>\n <div>\n <p className=\"font-medium\">{webhook.name}</p>\n <p className=\"text-xs text-text-secondary truncate max-w-xs\">\n {webhook.url}\n </p>\n </div>\n </div>\n ),\n },\n {\n key: \"events\",\n header: \"Events\",\n render: (webhook: WebhookType) => (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.slice(0, 2).map((event) => (\n <span\n key={event}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {event.split(\".\")[0]}\n </span>\n ))}\n {webhook.events.length > 2 && (\n <span className=\"text-xs text-text-secondary\">\n +{webhook.events.length - 2} more\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-2\">\n {getStatusIcon(webhook.status)}\n <StatusBadge status={webhook.status} type=\"webhook\" />\n </div>\n ),\n },\n {\n key: \"successRate\",\n header: \"Success Rate\",\n render: (webhook: WebhookType) => {\n const rate = calculateSuccessRate(webhook);\n return (\n <div className=\"w-24\">\n <div className=\"flex items-center justify-between text-xs mb-1\">\n <span\n className={cn(\n rate >= 95\n ? \"text-status-success-text\"\n : rate >= 80\n ? \"text-status-warning-text\"\n : \"text-status-error-text\",\n )}\n >\n {rate}%\n </span>\n </div>\n <div className=\"h-1.5 rounded-full bg-bg-sunken\">\n <div\n className={cn(\n \"h-full rounded-full transition-all\",\n rate >= 95\n ? \"bg-status-success-bg-subtle\"\n : rate >= 80\n ? \"bg-status-warning-bg-subtle\"\n : \"bg-status-error-bg-subtle\",\n )}\n style={{ width: `${rate}%` }}\n />\n </div>\n </div>\n );\n },\n },\n {\n key: \"deliveries\",\n header: \"Deliveries\",\n render: (webhook: WebhookType) => (\n <div className=\"text-sm\">\n <span className=\"text-status-success-text\">\n {webhook.successCount.toLocaleString()}\n </span>\n <span className=\"text-text-secondary\"> / </span>\n <span className=\"text-status-error-text\">\n {webhook.failureCount.toLocaleString()}\n </span>\n </div>\n ),\n },\n {\n key: \"lastTriggered\",\n header: \"Last Triggered\",\n render: (webhook: WebhookType) => (\n <span className=\"text-sm text-text-secondary\">\n {webhook.lastTriggeredAt\n ? formatRelativeTime(webhook.lastTriggeredAt)\n : \"Never\"}\n </span>\n ),\n },\n ];\n\n // Stats calculation\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w: WebhookType) => w.status === \"ACTIVE\").length,\n failed: webhooks.filter((w: WebhookType) => w.status === \"FAILED\").length,\n paused: webhooks.filter((w: WebhookType) => w.status === \"PAUSED\").length,\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n purpose={tr(\n \"devportal.webhooksAppPurpose\",\n \"These webhooks deliver this application's events to your endpoints. Add a URL, choose which events to subscribe to, and send a test delivery to confirm your server receives and verifies the signed payload.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/new`)}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createWebhook\", \"Create Webhook\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name or URL...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as WebhookStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"PAUSED\">Paused</option>\n <option value=\"FAILED\">Failed</option>\n <option value=\"DISABLED\">Disabled</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Webhook size={16} />\n <span className=\"text-sm\">Total Webhooks</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <CheckCircle size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <PauseCircle size={16} />\n <span className=\"text-sm\">Paused</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.paused}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <XCircle size={16} />\n <span className=\"text-sm\">Failed</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.failed}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredWebhooks.length} webhook\n {filteredWebhooks.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Webhooks Table */}\n {filteredWebhooks.length > 0 ? (\n <DataTable\n data={filteredWebhooks}\n columns={columns}\n keyField=\"id\"\n onRowClick={(webhook) =>\n handleNavigate(`/apps/${appId}/webhooks/${webhook.id}`)\n }\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n title={tr(\"devportal.noWebhooksFound\", \"No webhooks found\")}\n description={\n searchQuery || statusFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstWebhook\",\n \"Create your first webhook to receive real-time event notifications\",\n )\n }\n action={\n !searchQuery && !statusFilter ? (\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/new`)}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createWebhook\", \"Create Webhook\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\nfunction formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebhooksPage.js","names":[],"sources":["../../../src/devportal/pages/WebhooksPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState, useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Webhook,\n CheckCircle,\n XCircle,\n PauseCircle,\n AlertTriangle,\n Loader2,\n AlertCircle,\n AppWindow,\n} from \"lucide-react\";\nimport { useParams } from \"react-router\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { PermissionGuard } from \"../components\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type {\n Application,\n Webhook as WebhookType,\n WebhookStatus,\n} from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useGetApplicationsQuery,\n useGetApplicationQuery,\n useGetWebhooksQuery,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type DevportalWebhookFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Map GraphQL webhook to local Webhook type\n */\nfunction mapWebhook(webhook: DevportalWebhookFieldsFragment): WebhookType {\n return {\n id: webhook.id,\n applicationId: webhook.applicationId,\n name: webhook.name,\n description: webhook.description ?? undefined,\n url: webhook.url,\n secret: undefined,\n events: webhook.events ?? [],\n isActive: webhook.isActive,\n status: webhook.status as WebhookStatus,\n maxRetries: webhook.maxRetries ?? 3,\n retryDelay: webhook.retryDelay ?? 60,\n retryEnabled: webhook.retryEnabled ?? true,\n lastTriggeredAt: webhook.lastTriggeredAt ?? undefined,\n lastSuccessAt: webhook.lastSuccessAt ?? undefined,\n lastFailureAt: webhook.lastFailureAt ?? undefined,\n lastFailureReason: webhook.lastFailureReason ?? undefined,\n failureCount: webhook.failureCount ?? 0,\n successCount: webhook.successCount ?? 0,\n createdAt: webhook.createdAt,\n updatedAt: webhook.updatedAt,\n };\n}\n\n/**\n * Webhooks listing page\n *\n * Displays webhooks for a specific application.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const WebhooksPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const createPerm = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.WEBHOOK_CREATE,\n );\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<WebhookStatus | \"\">(\"\");\n\n // Fetch app name for breadcrumb when viewing webhooks for a specific app\n const { data: singleAppData } = useGetApplicationQuery({\n variables: { id: appId ?? \"\" },\n skip: !appId,\n fetchPolicy: \"cache-first\",\n });\n\n useEffect(() => {\n const name = singleAppData?.application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, singleAppData?.application?.name]);\n\n const {\n data: appsData,\n loading: appsLoading,\n error: appsError,\n refetch: refetchApps,\n } = useGetApplicationsQuery({\n variables: { limit: 100 },\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch webhooks from GraphQL API (only if we have an applicationId)\n const { data, loading, error, refetch } = useGetWebhooksQuery({\n variables: {\n applicationId: appId || \"\",\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const webhooks = data?.webhooks?.map(mapWebhook) ?? [];\n const allApplications: Application[] =\n appsData?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node as Application,\n ) ?? [];\n // Scope app picker to active org\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Filter webhooks\n const filteredWebhooks = webhooks.filter((webhook: WebhookType) => {\n const matchesSearch =\n !searchQuery ||\n webhook.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n webhook.url.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || webhook.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n\n const getStatusIcon = (status: WebhookStatus) => {\n switch (status) {\n case \"ACTIVE\":\n return <CheckCircle size={16} className=\"text-status-success-text\" />;\n case \"PAUSED\":\n return <PauseCircle size={16} className=\"text-status-warning-text\" />;\n case \"FAILED\":\n return <XCircle size={16} className=\"text-status-error-text\" />;\n case \"DISABLED\":\n return <AlertTriangle size={16} className=\"text-text-tertiary\" />;\n default:\n return null;\n }\n };\n\n const calculateSuccessRate = (webhook: WebhookType): number => {\n const total = webhook.successCount + webhook.failureCount;\n if (total === 0) return 100;\n return Math.round((webhook.successCount / total) * 100);\n };\n\n // If no applicationId, show prompt to select an application\n if (!appId) {\n const filteredApplications = applications.filter((application) => {\n if (!searchQuery) return true;\n\n const normalizedQuery = searchQuery.toLowerCase();\n return (\n application.name.toLowerCase().includes(normalizedQuery) ||\n (application.slug ?? \"\").toLowerCase().includes(normalizedQuery)\n );\n });\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApps\",\n \"Manage webhooks for your applications\",\n )}\n purpose={tr(\n \"devportal.webhooksPickerPurpose\",\n \"Webhooks push real-time events (installs, payments, app updates) to a URL you host, so your backend can react without polling. Pick an application to view, add, or test the webhook endpoints it delivers to.\",\n )}\n >\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.webhooksSelectAppHint\",\n \"Select an application below to view and manage its webhooks.\",\n )}\n </p>\n <div className=\"max-w-md\">\n <div className=\"relative\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search applications...\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n </div>\n\n {appsLoading ? (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : appsError ? (\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n Failed to load applications\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(appsError)}\n </p>\n <button\n onClick={() => refetchApps()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n Try Again\n </button>\n </div>\n ) : filteredApplications.length > 0 ? (\n <div className=\"space-y-2\">\n {filteredApplications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() =>\n handleNavigate(`/apps/${application.id}/webhooks`)\n }\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration={searchQuery ? \"empty-search\" : \"empty-data\"}\n icon={<AppWindow size={32} />}\n title=\"No applications available\"\n description={\n searchQuery\n ? \"No applications matched your search.\"\n : \"Create an application before configuring webhooks.\"\n }\n action={\n <button\n onClick={() => handleNavigate(\"/apps/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n Create Application\n </button>\n }\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingWebhooks\", \"Loading webhooks...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadWebhooks\", \"Failed to load webhooks\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"name\",\n header: \"Webhook\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n webhook.status === \"ACTIVE\"\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : webhook.status === \"FAILED\"\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : webhook.status === \"PAUSED\"\n ? \"bg-status-warning-bg-subtle text-status-warning-text\"\n : \"bg-bg-sunken text-text-tertiary\",\n )}\n >\n <Webhook size={20} />\n </div>\n <div>\n <p className=\"font-medium\">{webhook.name}</p>\n <p className=\"text-xs text-text-secondary truncate max-w-xs\">\n {webhook.url}\n </p>\n </div>\n </div>\n ),\n },\n {\n key: \"events\",\n header: \"Events\",\n render: (webhook: WebhookType) => (\n <div className=\"flex flex-wrap gap-1\">\n {webhook.events.slice(0, 2).map((event) => (\n <span\n key={event}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {event.split(\".\")[0]}\n </span>\n ))}\n {webhook.events.length > 2 && (\n <span className=\"text-xs text-text-secondary\">\n +{webhook.events.length - 2} more\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (webhook: WebhookType) => (\n <div className=\"flex items-center gap-2\">\n {getStatusIcon(webhook.status)}\n <StatusBadge status={webhook.status} type=\"webhook\" />\n </div>\n ),\n },\n {\n key: \"successRate\",\n header: \"Success Rate\",\n render: (webhook: WebhookType) => {\n const rate = calculateSuccessRate(webhook);\n return (\n <div className=\"w-24\">\n <div className=\"flex items-center justify-between text-xs mb-1\">\n <span\n className={cn(\n rate >= 95\n ? \"text-status-success-text\"\n : rate >= 80\n ? \"text-status-warning-text\"\n : \"text-status-error-text\",\n )}\n >\n {rate}%\n </span>\n </div>\n <div className=\"h-1.5 rounded-full bg-bg-sunken\">\n <div\n className={cn(\n \"h-full rounded-full transition-all\",\n rate >= 95\n ? \"bg-status-success-bg-subtle\"\n : rate >= 80\n ? \"bg-status-warning-bg-subtle\"\n : \"bg-status-error-bg-subtle\",\n )}\n style={{ width: `${rate}%` }}\n />\n </div>\n </div>\n );\n },\n },\n {\n key: \"deliveries\",\n header: \"Deliveries\",\n render: (webhook: WebhookType) => (\n <div className=\"text-sm\">\n <span className=\"text-status-success-text\">\n {webhook.successCount.toLocaleString()}\n </span>\n <span className=\"text-text-secondary\"> / </span>\n <span className=\"text-status-error-text\">\n {webhook.failureCount.toLocaleString()}\n </span>\n </div>\n ),\n },\n {\n key: \"lastTriggered\",\n header: \"Last Triggered\",\n render: (webhook: WebhookType) => (\n <span className=\"text-sm text-text-secondary\">\n {webhook.lastTriggeredAt\n ? formatRelativeTime(webhook.lastTriggeredAt)\n : \"Never\"}\n </span>\n ),\n },\n ];\n\n // Stats calculation\n const stats = {\n total: webhooks.length,\n active: webhooks.filter((w: WebhookType) => w.status === \"ACTIVE\").length,\n failed: webhooks.filter((w: WebhookType) => w.status === \"FAILED\").length,\n paused: webhooks.filter((w: WebhookType) => w.status === \"PAUSED\").length,\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n description={tr(\n \"devportal.manageWebhooksApp\",\n \"Manage webhooks for this application\",\n )}\n purpose={tr(\n \"devportal.webhooksAppPurpose\",\n \"These webhooks deliver this application's events to your endpoints. Add a URL, choose which events to subscribe to, and send a test delivery to confirm your server receives and verifies the signed payload.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/new`)}\n disabled={!createPerm.allowed}\n aria-disabled={!createPerm.allowed}\n title={\n !createPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.createWebhook\", \"Create Webhook\")}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name or URL...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as WebhookStatus | \"\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"PAUSED\">Paused</option>\n <option value=\"FAILED\">Failed</option>\n <option value=\"DISABLED\">Disabled</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Webhook size={16} />\n <span className=\"text-sm\">Total Webhooks</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <CheckCircle size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <PauseCircle size={16} />\n <span className=\"text-sm\">Paused</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.paused}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <XCircle size={16} />\n <span className=\"text-sm\">Failed</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.failed}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredWebhooks.length} webhook\n {filteredWebhooks.length !== 1 ? \"s\" : \"\"} found\n </p>\n\n {/* Webhooks Table */}\n {filteredWebhooks.length > 0 ? (\n <DataTable\n data={filteredWebhooks}\n columns={columns}\n keyField=\"id\"\n onRowClick={(webhook) =>\n handleNavigate(`/apps/${appId}/webhooks/${webhook.id}`)\n }\n />\n ) : (\n <EmptyState\n illustration=\"empty-notifications\"\n title={tr(\"devportal.noWebhooksFound\", \"No webhooks found\")}\n description={\n searchQuery || statusFilter\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.createFirstWebhook\",\n \"Create your first webhook to receive real-time event notifications\",\n )\n }\n action={\n !searchQuery && !statusFilter ? (\n <button\n onClick={() => handleNavigate(`/apps/${appId}/webhooks/new`)}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createWebhook\", \"Create Webhook\")}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\nfunction formatRelativeTime(dateString: string): string {\n const date = new Date(dateString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffMins < 1) return \"Just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n}\n"],"mappings":"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"}
|