@burdenoff/microfe-devportal 2026.828.1 → 2026.830.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/AdminDevportalRoot.d.ts.map +1 -1
- package/dist/devportal/AdminDevportalRoot.js +16 -19
- package/dist/devportal/AdminDevportalRoot.js.map +1 -1
- package/dist/devportal/DevportalRoot.d.ts.map +1 -1
- package/dist/devportal/DevportalRoot.js +36 -39
- package/dist/devportal/DevportalRoot.js.map +1 -1
- package/dist/devportal/components/AppCard.js +41 -41
- package/dist/devportal/components/ConfirmDialog.d.ts.map +1 -1
- package/dist/devportal/components/ConfirmDialog.js +19 -22
- package/dist/devportal/components/ConfirmDialog.js.map +1 -1
- package/dist/devportal/components/DataTable.d.ts.map +1 -1
- package/dist/devportal/components/DataTable.js +31 -34
- package/dist/devportal/components/DataTable.js.map +1 -1
- package/dist/devportal/components/FeatureFlaggedRoute.js +8 -8
- package/dist/devportal/components/FormField.d.ts.map +1 -1
- package/dist/devportal/components/FormField.js +26 -32
- package/dist/devportal/components/FormField.js.map +1 -1
- package/dist/devportal/components/GrantDelegationModal.js +64 -64
- package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
- package/dist/devportal/components/LoadingState.d.ts.map +1 -1
- package/dist/devportal/components/LoadingState.js +5 -8
- package/dist/devportal/components/LoadingState.js.map +1 -1
- package/dist/devportal/components/PageLayout.d.ts.map +1 -1
- package/dist/devportal/components/PageLayout.js +10 -13
- package/dist/devportal/components/PageLayout.js.map +1 -1
- package/dist/devportal/components/PermissionGuard.d.ts.map +1 -1
- package/dist/devportal/components/PermissionGuard.js +11 -14
- package/dist/devportal/components/PermissionGuard.js.map +1 -1
- package/dist/devportal/components/SecretDisplay.d.ts.map +1 -1
- package/dist/devportal/components/SecretDisplay.js +24 -30
- package/dist/devportal/components/SecretDisplay.js.map +1 -1
- package/dist/devportal/components/SectionNav.d.ts.map +1 -1
- package/dist/devportal/components/SectionNav.js +24 -27
- package/dist/devportal/components/SectionNav.js.map +1 -1
- package/dist/devportal/components/StatusBadge.d.ts.map +1 -1
- package/dist/devportal/components/StatusBadge.js +24 -33
- package/dist/devportal/components/StatusBadge.js.map +1 -1
- package/dist/devportal/index.d.ts +1 -0
- package/dist/devportal/index.d.ts.map +1 -1
- package/dist/devportal/index.js +1 -0
- package/dist/devportal/pages/AdminReviewPage.js +172 -172
- package/dist/devportal/pages/AnalyticsDashboardPage.js +43 -43
- package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/devportal/pages/ApiKeysPage.js +119 -119
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/AppLogsPage.js +45 -45
- package/dist/devportal/pages/ApplicationDetailPage.js +607 -607
- package/dist/devportal/pages/ApplicationVersionsPage.js +13 -13
- package/dist/devportal/pages/ApplicationsPage.d.ts.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +104 -101
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +45 -45
- package/dist/devportal/pages/CreateApplicationPage.js +38 -38
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +52 -52
- package/dist/devportal/pages/CreateOrganizationPage.js +26 -26
- package/dist/devportal/pages/CreateWebhookPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js +48 -48
- package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
- package/dist/devportal/pages/DashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/DashboardPage.js +66 -72
- package/dist/devportal/pages/DashboardPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +174 -174
- package/dist/devportal/pages/DocumentationPage.js +93 -93
- package/dist/devportal/pages/EditApplicationPage.js +14 -14
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.js +76 -76
- package/dist/devportal/pages/OAuthClientDetailPage.js +75 -75
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +64 -64
- package/dist/devportal/pages/OrganizationDetailPage.js +189 -189
- package/dist/devportal/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js +10 -10
- package/dist/devportal/pages/OrganizationsPage.d.ts.map +1 -1
- package/dist/devportal/pages/OrganizationsPage.js +163 -154
- package/dist/devportal/pages/OrganizationsPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.js +104 -104
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/PublisherProfilePage.js +55 -55
- package/dist/devportal/pages/ResourcesPage.js +60 -60
- package/dist/devportal/pages/RevenueDashboardPage.js +60 -60
- package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +83 -83
- package/dist/devportal/pages/SellerItemsPage.js +163 -163
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/pages/SellerOrdersPage.js +63 -63
- package/dist/devportal/pages/SettingsPage.js +21 -21
- package/dist/devportal/pages/SetupPage.js +183 -183
- package/dist/devportal/pages/SetupPage.js.map +1 -1
- package/dist/devportal/pages/StoreSubmissionPage.js +5 -5
- package/dist/devportal/pages/WebhookDetailPage.js +123 -123
- package/dist/devportal/pages/WebhookDetailPage.js.map +1 -1
- package/dist/devportal/pages/WebhooksPage.js +118 -118
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/utils/i18n.d.ts +9 -2
- package/dist/devportal/utils/i18n.d.ts.map +1 -1
- package/dist/devportal/utils/i18n.js +13 -10
- package/dist/devportal/utils/i18n.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/translations/en.d.ts +1251 -0
- package/dist/translations/en.d.ts.map +1 -0
- package/dist/translations/en.js +1235 -0
- package/dist/translations/en.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerItemsPage.js","names":[],"sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n Package,\n Loader2,\n AlertCircle,\n Edit,\n Tag,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n | \"DRAFT\"\n | \"PENDING_REVIEW\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"INACTIVE\";\n\ntype PhysicalProductType =\n | \"MERCHANDISE\"\n | \"HARDWARE\"\n | \"ACCESSORY\"\n | \"BOOK\"\n | \"EQUIPMENT\"\n | \"APPAREL\"\n | \"COLLECTIBLE\"\n | \"CONSUMABLE\";\n\ninterface PhysicalProductDetail {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n sku?: string | null;\n brand?: string | null;\n icon?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Digital product types to exclude even if `nature: PHYSICAL` is set wrong.\n */\nconst DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n\nconst PRODUCT_TYPE_LABELS: Record<PhysicalProductType, string> = {\n MERCHANDISE: \"Merchandise\",\n HARDWARE: \"Hardware\",\n ACCESSORY: \"Accessory\",\n BOOK: \"Book\",\n EQUIPMENT: \"Equipment\",\n APPAREL: \"Apparel\",\n COLLECTIBLE: \"Collectible\",\n CONSUMABLE: \"Consumable\",\n};\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\nconst STATUS_OPTIONS: { value: ProductStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Status\" },\n { value: \"DRAFT\", label: \"Draft\" },\n { value: \"PENDING_REVIEW\", label: \"Pending Review\" },\n { value: \"PUBLISHED\", label: \"Published\" },\n { value: \"REJECTED\", label: \"Rejected\" },\n { value: \"INACTIVE\", label: \"Inactive\" },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\nfunction resolveTypeLabel(type: string): string {\n return (\n PRODUCT_TYPE_LABELS[type as PhysicalProductType] ?? type.replace(/_/g, \" \")\n );\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst SELLER_PUBLISHER_QUERY = `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_ITEMS_QUERY = `\n query SellerItems($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 100, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n sku\n brand\n icon\n createdAt\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerItemsPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = 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\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [publisherLoading, setPublisherLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProductDetail[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<ProductStatus | \"\">(\"\");\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Fetch publisher first\n useEffect(() => {\n let cancelled = false;\n setPublisherLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: SELLER_PUBLISHER_QUERY }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: { myPublisher?: { id: string } | null };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } catch (err) {\n if (!cancelled) {\n setError(\n err instanceof Error ? err.message : \"Failed to load publisher\",\n );\n }\n } finally {\n if (!cancelled) setPublisherLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken, retryToken]);\n\n // Fetch products once publisher is resolved\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_ITEMS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProductDetail, \"status\"> & { status: string }\n >;\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setProducts(\n (json.data?.products ?? []).map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err.message : \"Failed to load items\");\n }\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken, retryToken]);\n\n // Exclude digital types defensively, then apply client-side filters\n const physicalItems = useMemo(\n () => products.filter((p) => !DIGITAL_TYPES.has(p.type)),\n [products],\n );\n\n const filteredItems = useMemo(\n () =>\n physicalItems.filter((item) => {\n const matchesSearch =\n !searchQuery ||\n item.name.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || item.status === statusFilter;\n return matchesSearch && matchesStatus;\n }),\n [physicalItems, searchQuery, statusFilter],\n );\n\n const stats = useMemo(\n () => ({\n total: physicalItems.length,\n published: physicalItems.filter((i) => i.status === \"PUBLISHED\").length,\n pendingReview: physicalItems.filter((i) => i.status === \"PENDING_REVIEW\")\n .length,\n outOfStock: physicalItems.filter((i) => i.stockQuantity === 0).length,\n }),\n [physicalItems],\n );\n\n const isInitialLoading = publisherLoading && products.length === 0 && !error;\n\n // Full-page loading state\n if (isInitialLoading) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\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.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Full-page error state\n if (error && products.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\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.failedToLoadItems\", \"Failed to load items\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{error}</p>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\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 transition-colors\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const hasActiveFilters = Boolean(searchQuery || statusFilter);\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/seller/items/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 transition-colors 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.listAnItem\", \"List an Item\")}\n </button>\n }\n >\n {/* Summary stats */}\n <EmphasisPanel className=\"mb-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalItems\", \"Total Items\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.total\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.published\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-warning-text\">\n {tr(\"devportal.pendingReview\", \"Pending Review\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.pendingReview\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-error-text\">\n {tr(\"devportal.outOfStock\", \"Out of Stock\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.outOfStock\n )}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Filters + view toggle */}\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-3\">\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={tr(\"devportal.searchItems\", \"Search items...\")}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface pl-9 pr-4 py-2 text-sm text-text-primary placeholder:text-text-muted 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 ProductStatus | \"\")\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View toggle */}\n <div className=\"flex items-center gap-1 rounded-md border border-border-seam p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"grid\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.gridView\", \"Grid view\")}\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"list\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.listView\", \"List view\")}\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Results count */}\n {physicalItems.length > 0 && (\n <p className=\"text-sm text-text-secondary\">\n {filteredItems.length} item\n {filteredItems.length !== 1 ? \"s\" : \"\"} found\n </p>\n )}\n\n {/* Content */}\n {!publisherId && !publisherLoading ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Package size={32} />}\n title={tr(\n \"devportal.noPublisherProfileYet\",\n \"No publisher profile yet\",\n )}\n description={tr(\n \"devportal.listAnItemToGetStarted\",\n \"List an item to get started. Your publisher profile will be created automatically.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n ) : productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n ) : filteredItems.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredItems.map((item) => (\n <ProductItemCard\n key={item.id}\n item={item}\n onEdit={() => handleNavigate(`/seller/items/${item.id}`)}\n />\n ))}\n </div>\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 Item\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Price\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Stock\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-right font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredItems.map((item) => {\n const badge =\n STATUS_BADGE_STYLES[item.status] ??\n STATUS_BADGE_STYLES.DRAFT;\n return (\n <tr\n key={item.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 <div className=\"flex items-center gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-8 w-8 rounded-lg object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken shrink-0\">\n <Package\n size={14}\n className=\"text-text-muted\"\n />\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">\n {item.name}\n </p>\n {item.sku && (\n <p className=\"text-xs text-text-muted\">\n SKU: {item.sku}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </td>\n <td className=\"px-4 py-3 text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </td>\n <td className=\"px-4 py-3\">\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">\n Out of stock\n </span>\n ) : (\n <span className=\"text-text-primary tabular-nums\">\n {item.stockQuantity}\n </span>\n )}\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 </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() =>\n handleNavigate(`/seller/items/${item.id}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n {tr(\"devportal.edit\", \"Edit\")}\n </button>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )\n ) : physicalItems.length > 0 && hasActiveFilters ? (\n <EmptyState\n illustration=\"empty-search\"\n title={tr(\n \"devportal.noItemsMatchYourSearch\",\n \"No items match your search\",\n )}\n description={tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your search or filters\",\n )}\n />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={tr(\"devportal.noItemsYet\", \"No items yet\")}\n description={tr(\n \"devportal.addYourFirstPhysicalProduct\",\n \"Add your first physical product listing to start selling\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst ProductItemCard: FC<{\n item: PhysicalProductDetail;\n onEdit: () => void;\n}> = ({ item, onEdit }) => {\n const badge = STATUS_BADGE_STYLES[item.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard treatment=\"insight\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-20 w-20 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-20 w-20 items-center justify-center rounded-xl bg-bg-sunken shrink-0\">\n <Package size={32} className=\"text-text-muted\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary truncate\">\n {item.name}\n </p>\n {item.brand && (\n <p className=\"text-xs text-text-muted mt-0.5\">{item.brand}</p>\n )}\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPrice(item.price, item.currency)}\n </p>\n <div className=\"flex items-center gap-2 mt-2 flex-wrap\">\n <span\n className={`rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n <span className=\"rounded-full px-2 py-0.5 text-xs font-medium bg-bg-muted text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-subtle\">\n <div className=\"flex items-center gap-1.5 text-sm\">\n <Tag size={12} className=\"text-text-muted shrink-0\" />\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">Out of stock</span>\n ) : (\n <span className=\"text-text-secondary\">\n {item.stockQuantity} in stock\n </span>\n )}\n </div>\n <button\n onClick={onEdit}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n Edit\n </button>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerItemsPage.js","names":[],"sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n Package,\n Loader2,\n AlertCircle,\n Edit,\n Tag,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n | \"DRAFT\"\n | \"PENDING_REVIEW\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"INACTIVE\";\n\ntype PhysicalProductType =\n | \"MERCHANDISE\"\n | \"HARDWARE\"\n | \"ACCESSORY\"\n | \"BOOK\"\n | \"EQUIPMENT\"\n | \"APPAREL\"\n | \"COLLECTIBLE\"\n | \"CONSUMABLE\";\n\ninterface PhysicalProductDetail {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n sku?: string | null;\n brand?: string | null;\n icon?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Digital product types to exclude even if `nature: PHYSICAL` is set wrong.\n */\nconst DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n\nconst PRODUCT_TYPE_LABELS: Record<PhysicalProductType, string> = {\n MERCHANDISE: \"Merchandise\",\n HARDWARE: \"Hardware\",\n ACCESSORY: \"Accessory\",\n BOOK: \"Book\",\n EQUIPMENT: \"Equipment\",\n APPAREL: \"Apparel\",\n COLLECTIBLE: \"Collectible\",\n CONSUMABLE: \"Consumable\",\n};\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\nconst STATUS_OPTIONS: { value: ProductStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Status\" },\n { value: \"DRAFT\", label: \"Draft\" },\n { value: \"PENDING_REVIEW\", label: \"Pending Review\" },\n { value: \"PUBLISHED\", label: \"Published\" },\n { value: \"REJECTED\", label: \"Rejected\" },\n { value: \"INACTIVE\", label: \"Inactive\" },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\nfunction resolveTypeLabel(type: string): string {\n return (\n PRODUCT_TYPE_LABELS[type as PhysicalProductType] ?? type.replace(/_/g, \" \")\n );\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst SELLER_PUBLISHER_QUERY = `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_ITEMS_QUERY = `\n query SellerItems($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 100, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n sku\n brand\n icon\n createdAt\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerItemsPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = 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\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [publisherLoading, setPublisherLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProductDetail[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<ProductStatus | \"\">(\"\");\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Fetch publisher first\n useEffect(() => {\n let cancelled = false;\n setPublisherLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: SELLER_PUBLISHER_QUERY }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: { myPublisher?: { id: string } | null };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } catch (err) {\n if (!cancelled) {\n setError(\n err instanceof Error ? err.message : \"Failed to load publisher\",\n );\n }\n } finally {\n if (!cancelled) setPublisherLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken, retryToken]);\n\n // Fetch products once publisher is resolved\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_ITEMS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProductDetail, \"status\"> & { status: string }\n >;\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setProducts(\n (json.data?.products ?? []).map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err.message : \"Failed to load items\");\n }\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken, retryToken]);\n\n // Exclude digital types defensively, then apply client-side filters\n const physicalItems = useMemo(\n () => products.filter((p) => !DIGITAL_TYPES.has(p.type)),\n [products],\n );\n\n const filteredItems = useMemo(\n () =>\n physicalItems.filter((item) => {\n const matchesSearch =\n !searchQuery ||\n item.name.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || item.status === statusFilter;\n return matchesSearch && matchesStatus;\n }),\n [physicalItems, searchQuery, statusFilter],\n );\n\n const stats = useMemo(\n () => ({\n total: physicalItems.length,\n published: physicalItems.filter((i) => i.status === \"PUBLISHED\").length,\n pendingReview: physicalItems.filter((i) => i.status === \"PENDING_REVIEW\")\n .length,\n outOfStock: physicalItems.filter((i) => i.stockQuantity === 0).length,\n }),\n [physicalItems],\n );\n\n const isInitialLoading = publisherLoading && products.length === 0 && !error;\n\n // Full-page loading state\n if (isInitialLoading) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\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.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Full-page error state\n if (error && products.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\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.failedToLoadItems\", \"Failed to load items\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{error}</p>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\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 transition-colors\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const hasActiveFilters = Boolean(searchQuery || statusFilter);\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/seller/items/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 transition-colors 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.listAnItem\", \"List an Item\")}\n </button>\n }\n >\n {/* Summary stats */}\n <EmphasisPanel className=\"mb-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalItems\", \"Total Items\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.total\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.published\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-warning-text\">\n {tr(\"devportal.pendingReview\", \"Pending Review\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.pendingReview\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-error-text\">\n {tr(\"devportal.outOfStock\", \"Out of Stock\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.outOfStock\n )}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Filters + view toggle */}\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-3\">\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={tr(\"devportal.searchItems\", \"Search items...\")}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface pl-9 pr-4 py-2 text-sm text-text-primary placeholder:text-text-muted 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 ProductStatus | \"\")\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View toggle */}\n <div className=\"flex items-center gap-1 rounded-md border border-border-seam p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"grid\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.gridView\", \"Grid view\")}\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"list\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.listView\", \"List view\")}\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Results count */}\n {physicalItems.length > 0 && (\n <p className=\"text-sm text-text-secondary\">\n {filteredItems.length} item\n {filteredItems.length !== 1 ? \"s\" : \"\"} found\n </p>\n )}\n\n {/* Content */}\n {!publisherId && !publisherLoading ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Package size={32} />}\n title={tr(\n \"devportal.noPublisherProfileYet\",\n \"No publisher profile yet\",\n )}\n description={tr(\n \"devportal.listAnItemToGetStarted\",\n \"List an item to get started. Your publisher profile will be created automatically.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n ) : productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n ) : filteredItems.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredItems.map((item) => (\n <ProductItemCard\n key={item.id}\n item={item}\n onEdit={() => handleNavigate(`/seller/items/${item.id}`)}\n />\n ))}\n </div>\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 Item\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Price\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Stock\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-right font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredItems.map((item) => {\n const badge =\n STATUS_BADGE_STYLES[item.status] ??\n STATUS_BADGE_STYLES.DRAFT;\n return (\n <tr\n key={item.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 <div className=\"flex items-center gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-8 w-8 rounded-lg object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken shrink-0\">\n <Package\n size={14}\n className=\"text-text-muted\"\n />\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">\n {item.name}\n </p>\n {item.sku && (\n <p className=\"text-xs text-text-muted\">\n SKU: {item.sku}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </td>\n <td className=\"px-4 py-3 text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </td>\n <td className=\"px-4 py-3\">\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">\n Out of stock\n </span>\n ) : (\n <span className=\"text-text-primary tabular-nums\">\n {item.stockQuantity}\n </span>\n )}\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 </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() =>\n handleNavigate(`/seller/items/${item.id}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n {tr(\"devportal.edit\", \"Edit\")}\n </button>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )\n ) : physicalItems.length > 0 && hasActiveFilters ? (\n <EmptyState\n illustration=\"empty-search\"\n title={tr(\n \"devportal.noItemsMatchYourSearch\",\n \"No items match your search\",\n )}\n description={tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your search or filters\",\n )}\n />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={tr(\"devportal.noItemsYet\", \"No items yet\")}\n description={tr(\n \"devportal.addYourFirstPhysicalProduct\",\n \"Add your first physical product listing to start selling\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst ProductItemCard: FC<{\n item: PhysicalProductDetail;\n onEdit: () => void;\n}> = ({ item, onEdit }) => {\n const badge = STATUS_BADGE_STYLES[item.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard treatment=\"insight\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-20 w-20 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-20 w-20 items-center justify-center rounded-xl bg-bg-sunken shrink-0\">\n <Package size={32} className=\"text-text-muted\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary truncate\">\n {item.name}\n </p>\n {item.brand && (\n <p className=\"text-xs text-text-muted mt-0.5\">{item.brand}</p>\n )}\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPrice(item.price, item.currency)}\n </p>\n <div className=\"flex items-center gap-2 mt-2 flex-wrap\">\n <span\n className={`rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n <span className=\"rounded-full px-2 py-0.5 text-xs font-medium bg-bg-muted text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-subtle\">\n <div className=\"flex items-center gap-1.5 text-sm\">\n <Tag size={12} className=\"text-text-muted shrink-0\" />\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">Out of stock</span>\n ) : (\n <span className=\"text-text-secondary\">\n {item.stockQuantity} in stock\n </span>\n )}\n </div>\n <button\n onClick={onEdit}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n Edit\n </button>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,9 +4,9 @@ import { PageLayout as n } from "../components/PageLayout.js";
|
|
|
4
4
|
import { PermissionGuard as r } from "../components/PermissionGuard.js";
|
|
5
5
|
import "../components/index.js";
|
|
6
6
|
import { useEffect as i, useMemo as a, useState as o } from "react";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { AlertCircle as s, ChevronDown as c, Loader2 as l, Package as u, ShoppingBag as d } from "lucide-react";
|
|
8
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
9
|
+
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
10
|
//#region src/devportal/pages/SellerOrdersPage.tsx
|
|
11
11
|
var g = {
|
|
12
12
|
PENDING: {
|
|
@@ -87,7 +87,7 @@ function S(e, t) {
|
|
|
87
87
|
return e ? `#${e.slice(0, 8).toUpperCase()}` : `#${t.slice(0, 8).toUpperCase()}`;
|
|
88
88
|
}
|
|
89
89
|
var C = () => {
|
|
90
|
-
let { apiGatewayUrl: C, authToken: w } = e(), { t: T } =
|
|
90
|
+
let { apiGatewayUrl: C, authToken: w } = e(), { t: T } = h(), E = (e, t) => {
|
|
91
91
|
let n = T(e);
|
|
92
92
|
return n === e ? t : n;
|
|
93
93
|
}, D = C ?? "/global/graphql", [O, k] = o(!0), [A, j] = o([]), [M, N] = o(null), [P, F] = o(!1), [I, L] = o([]), [R, z] = o(0), [B, V] = o(null), [H, U] = o(0), [W, G] = o(""), [K, q] = o(0), J = a(() => ({
|
|
@@ -165,64 +165,64 @@ var C = () => {
|
|
|
165
165
|
refunded: e.filter((e) => e.status === "REFUNDED" || e.status === "PARTIAL_REFUND").length
|
|
166
166
|
};
|
|
167
167
|
}, [Y, R]);
|
|
168
|
-
return O ? /* @__PURE__ */
|
|
168
|
+
return O ? /* @__PURE__ */ p(n, {
|
|
169
169
|
title: E("devportal.orders", "Orders"),
|
|
170
|
-
children: /* @__PURE__ */
|
|
170
|
+
children: /* @__PURE__ */ p("div", {
|
|
171
171
|
className: "flex items-center justify-center py-20",
|
|
172
|
-
children: /* @__PURE__ */
|
|
172
|
+
children: /* @__PURE__ */ p(l, { className: "h-8 w-8 animate-spin text-text-secondary" })
|
|
173
173
|
})
|
|
174
|
-
}) : A.length === 0 ? /* @__PURE__ */
|
|
174
|
+
}) : A.length === 0 ? /* @__PURE__ */ p(n, {
|
|
175
175
|
title: E("devportal.orders", "Orders"),
|
|
176
176
|
description: E("devportal.ordersDesc", "Orders placed by customers for your physical items."),
|
|
177
|
-
children: /* @__PURE__ */
|
|
177
|
+
children: /* @__PURE__ */ p(t, {
|
|
178
178
|
illustration: "empty-generic",
|
|
179
|
-
icon: /* @__PURE__ */
|
|
179
|
+
icon: /* @__PURE__ */ p(d, { size: 32 }),
|
|
180
180
|
title: E("devportal.noOrgForOrders", "No organization yet"),
|
|
181
181
|
description: E("devportal.createOrgToSell", "Create a developer organization to start selling physical products and receive orders.")
|
|
182
182
|
})
|
|
183
|
-
}) : /* @__PURE__ */
|
|
183
|
+
}) : /* @__PURE__ */ p(r, {
|
|
184
184
|
authOnly: !0,
|
|
185
|
-
children: /* @__PURE__ */
|
|
185
|
+
children: /* @__PURE__ */ m(n, {
|
|
186
186
|
title: E("devportal.orders", "Orders"),
|
|
187
187
|
description: E("devportal.ordersDesc", "Orders placed by customers for your physical items."),
|
|
188
188
|
children: [
|
|
189
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ m("div", {
|
|
190
190
|
className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-6",
|
|
191
|
-
children: [A.length > 1 && /* @__PURE__ */
|
|
191
|
+
children: [A.length > 1 && /* @__PURE__ */ m("div", {
|
|
192
192
|
className: "relative",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
193
|
+
children: [/* @__PURE__ */ p("select", {
|
|
194
194
|
value: M ?? "",
|
|
195
195
|
onChange: (e) => {
|
|
196
196
|
N(e.target.value), q(0);
|
|
197
197
|
},
|
|
198
198
|
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",
|
|
199
|
-
children: A.map((e) => /* @__PURE__ */
|
|
199
|
+
children: A.map((e) => /* @__PURE__ */ p("option", {
|
|
200
200
|
value: e.id,
|
|
201
201
|
children: e.name
|
|
202
202
|
}, e.id))
|
|
203
|
-
}), /* @__PURE__ */
|
|
203
|
+
}), /* @__PURE__ */ p(c, {
|
|
204
204
|
size: 14,
|
|
205
205
|
className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted"
|
|
206
206
|
})]
|
|
207
|
-
}), /* @__PURE__ */
|
|
207
|
+
}), /* @__PURE__ */ m("div", {
|
|
208
208
|
className: "relative",
|
|
209
|
-
children: [/* @__PURE__ */
|
|
209
|
+
children: [/* @__PURE__ */ p("select", {
|
|
210
210
|
value: W,
|
|
211
211
|
onChange: (e) => {
|
|
212
212
|
G(e.target.value), q(0);
|
|
213
213
|
},
|
|
214
214
|
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",
|
|
215
|
-
children: _.map((e) => /* @__PURE__ */
|
|
215
|
+
children: _.map((e) => /* @__PURE__ */ p("option", {
|
|
216
216
|
value: e.value,
|
|
217
217
|
children: e.label
|
|
218
218
|
}, e.value))
|
|
219
|
-
}), /* @__PURE__ */
|
|
219
|
+
}), /* @__PURE__ */ p(c, {
|
|
220
220
|
size: 14,
|
|
221
221
|
className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted"
|
|
222
222
|
})]
|
|
223
223
|
})]
|
|
224
224
|
}),
|
|
225
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ p("div", {
|
|
226
226
|
className: "grid grid-cols-2 gap-3 sm:grid-cols-4 mb-6",
|
|
227
227
|
children: [
|
|
228
228
|
{
|
|
@@ -244,119 +244,119 @@ var C = () => {
|
|
|
244
244
|
value: X.refunded,
|
|
245
245
|
color: "text-status-error-text"
|
|
246
246
|
}
|
|
247
|
-
].map((e) => /* @__PURE__ */
|
|
247
|
+
].map((e) => /* @__PURE__ */ m("div", {
|
|
248
248
|
className: "rounded-lg border border-border-seam bg-bg-surface px-4 py-3",
|
|
249
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ p("p", {
|
|
250
250
|
className: `text-xs ${e.color ?? "text-text-secondary"}`,
|
|
251
251
|
children: e.label
|
|
252
|
-
}), /* @__PURE__ */
|
|
252
|
+
}), /* @__PURE__ */ p("p", {
|
|
253
253
|
className: "mt-1 text-xl font-semibold tabular-nums",
|
|
254
|
-
children: P ? /* @__PURE__ */
|
|
254
|
+
children: P ? /* @__PURE__ */ p(l, { className: "h-4 w-4 animate-spin text-text-secondary" }) : e.value
|
|
255
255
|
})]
|
|
256
256
|
}, e.label))
|
|
257
257
|
}),
|
|
258
|
-
B && /* @__PURE__ */
|
|
258
|
+
B && /* @__PURE__ */ m("div", {
|
|
259
259
|
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",
|
|
260
260
|
children: [
|
|
261
|
-
/* @__PURE__ */
|
|
261
|
+
/* @__PURE__ */ p(s, {
|
|
262
262
|
size: 16,
|
|
263
263
|
className: "shrink-0 text-status-error-text"
|
|
264
264
|
}),
|
|
265
|
-
/* @__PURE__ */
|
|
265
|
+
/* @__PURE__ */ p("span", {
|
|
266
266
|
className: "text-status-error-text",
|
|
267
267
|
children: B
|
|
268
268
|
}),
|
|
269
|
-
/* @__PURE__ */
|
|
269
|
+
/* @__PURE__ */ p("button", {
|
|
270
270
|
onClick: () => U((e) => e + 1),
|
|
271
271
|
className: "ml-auto text-xs font-medium text-status-error-text underline",
|
|
272
272
|
children: "Retry"
|
|
273
273
|
})
|
|
274
274
|
]
|
|
275
275
|
}),
|
|
276
|
-
P ? /* @__PURE__ */
|
|
276
|
+
P ? /* @__PURE__ */ p("div", {
|
|
277
277
|
className: "flex items-center justify-center py-16",
|
|
278
|
-
children: /* @__PURE__ */
|
|
279
|
-
}) : Y.length === 0 ? /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ p(l, { className: "h-6 w-6 animate-spin text-text-secondary" })
|
|
279
|
+
}) : Y.length === 0 ? /* @__PURE__ */ p(t, {
|
|
280
280
|
illustration: "empty-data",
|
|
281
|
-
icon: /* @__PURE__ */
|
|
281
|
+
icon: /* @__PURE__ */ p(u, { size: 32 }),
|
|
282
282
|
title: W ? E("devportal.noOrdersForFilter", "No orders match this filter") : E("devportal.noOrdersYet", "No orders yet"),
|
|
283
283
|
description: W ? E("devportal.tryDifferentFilter", "Try selecting a different status filter") : E("devportal.ordersWillAppear", "Orders placed for your physical items will appear here once customers make purchases.")
|
|
284
|
-
}) : /* @__PURE__ */
|
|
284
|
+
}) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", {
|
|
285
285
|
className: "overflow-x-auto rounded-lg border border-border-seam",
|
|
286
|
-
children: /* @__PURE__ */
|
|
286
|
+
children: /* @__PURE__ */ m("table", {
|
|
287
287
|
className: "w-full text-sm",
|
|
288
|
-
children: [/* @__PURE__ */
|
|
288
|
+
children: [/* @__PURE__ */ p("thead", { children: /* @__PURE__ */ m("tr", {
|
|
289
289
|
className: "border-b border-border-seam bg-bg-sunken",
|
|
290
290
|
children: [
|
|
291
|
-
/* @__PURE__ */
|
|
291
|
+
/* @__PURE__ */ p("th", {
|
|
292
292
|
className: "px-4 py-3 text-left font-medium text-text-secondary",
|
|
293
293
|
children: "Order"
|
|
294
294
|
}),
|
|
295
|
-
/* @__PURE__ */
|
|
295
|
+
/* @__PURE__ */ p("th", {
|
|
296
296
|
className: "px-4 py-3 text-left font-medium text-text-secondary",
|
|
297
297
|
children: "Product ID"
|
|
298
298
|
}),
|
|
299
|
-
/* @__PURE__ */
|
|
299
|
+
/* @__PURE__ */ p("th", {
|
|
300
300
|
className: "px-4 py-3 text-left font-medium text-text-secondary",
|
|
301
301
|
children: "Buyer"
|
|
302
302
|
}),
|
|
303
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ p("th", {
|
|
304
304
|
className: "px-4 py-3 text-right font-medium text-text-secondary",
|
|
305
305
|
children: "Gross"
|
|
306
306
|
}),
|
|
307
|
-
/* @__PURE__ */
|
|
307
|
+
/* @__PURE__ */ p("th", {
|
|
308
308
|
className: "px-4 py-3 text-right font-medium text-text-secondary",
|
|
309
309
|
children: "Your Earnings"
|
|
310
310
|
}),
|
|
311
|
-
/* @__PURE__ */
|
|
311
|
+
/* @__PURE__ */ p("th", {
|
|
312
312
|
className: "px-4 py-3 text-left font-medium text-text-secondary",
|
|
313
313
|
children: "Status"
|
|
314
314
|
}),
|
|
315
|
-
/* @__PURE__ */
|
|
315
|
+
/* @__PURE__ */ p("th", {
|
|
316
316
|
className: "px-4 py-3 text-left font-medium text-text-secondary",
|
|
317
317
|
children: "Date"
|
|
318
318
|
})
|
|
319
319
|
]
|
|
320
|
-
}) }), /* @__PURE__ */
|
|
320
|
+
}) }), /* @__PURE__ */ p("tbody", { children: Y.map((e) => {
|
|
321
321
|
let t = g[e.status] ?? g.PENDING;
|
|
322
|
-
return /* @__PURE__ */
|
|
322
|
+
return /* @__PURE__ */ m("tr", {
|
|
323
323
|
className: "border-b border-border-subtle last:border-0 hover:bg-bg-sunken/50 transition-colors",
|
|
324
324
|
children: [
|
|
325
|
-
/* @__PURE__ */
|
|
325
|
+
/* @__PURE__ */ p("td", {
|
|
326
326
|
className: "px-4 py-3",
|
|
327
|
-
children: /* @__PURE__ */
|
|
327
|
+
children: /* @__PURE__ */ p("span", {
|
|
328
328
|
className: "font-mono text-xs font-semibold text-text-primary",
|
|
329
329
|
children: S(e.storeOrderId, e.id)
|
|
330
330
|
})
|
|
331
331
|
}),
|
|
332
|
-
/* @__PURE__ */
|
|
332
|
+
/* @__PURE__ */ p("td", {
|
|
333
333
|
className: "px-4 py-3",
|
|
334
|
-
children: /* @__PURE__ */
|
|
334
|
+
children: /* @__PURE__ */ p("span", {
|
|
335
335
|
className: "font-mono text-xs text-text-secondary",
|
|
336
336
|
children: e.storeProductId ? `…${e.storeProductId.slice(-8)}` : "—"
|
|
337
337
|
})
|
|
338
338
|
}),
|
|
339
|
-
/* @__PURE__ */
|
|
339
|
+
/* @__PURE__ */ p("td", {
|
|
340
340
|
className: "px-4 py-3",
|
|
341
|
-
children: /* @__PURE__ */
|
|
341
|
+
children: /* @__PURE__ */ p("span", {
|
|
342
342
|
className: "font-mono text-xs text-text-secondary",
|
|
343
343
|
children: `…${e.buyerUserId.slice(-8)}`
|
|
344
344
|
})
|
|
345
345
|
}),
|
|
346
|
-
/* @__PURE__ */
|
|
346
|
+
/* @__PURE__ */ p("td", {
|
|
347
347
|
className: "px-4 py-3 text-right tabular-nums text-text-primary",
|
|
348
348
|
children: b(e.grossAmount, e.currency)
|
|
349
349
|
}),
|
|
350
|
-
/* @__PURE__ */
|
|
350
|
+
/* @__PURE__ */ p("td", {
|
|
351
351
|
className: "px-4 py-3 text-right tabular-nums text-status-success-text",
|
|
352
352
|
children: b(e.netAmount, e.currency)
|
|
353
353
|
}),
|
|
354
|
-
/* @__PURE__ */
|
|
354
|
+
/* @__PURE__ */ m("td", {
|
|
355
355
|
className: "px-4 py-3",
|
|
356
|
-
children: [/* @__PURE__ */
|
|
356
|
+
children: [/* @__PURE__ */ p("span", {
|
|
357
357
|
className: `rounded-full px-2.5 py-0.5 text-xs font-medium ${t.className}`,
|
|
358
358
|
children: t.label
|
|
359
|
-
}), e.refundAmount != null && e.refundAmount > 0 && /* @__PURE__ */
|
|
359
|
+
}), e.refundAmount != null && e.refundAmount > 0 && /* @__PURE__ */ m("p", {
|
|
360
360
|
className: "mt-0.5 text-[11px] text-status-error-text",
|
|
361
361
|
children: [
|
|
362
362
|
"Refunded",
|
|
@@ -365,7 +365,7 @@ var C = () => {
|
|
|
365
365
|
]
|
|
366
366
|
})]
|
|
367
367
|
}),
|
|
368
|
-
/* @__PURE__ */
|
|
368
|
+
/* @__PURE__ */ p("td", {
|
|
369
369
|
className: "px-4 py-3 text-xs text-text-secondary whitespace-nowrap",
|
|
370
370
|
children: x(e.purchasedAt)
|
|
371
371
|
})
|
|
@@ -373,9 +373,9 @@ var C = () => {
|
|
|
373
373
|
}, e.id);
|
|
374
374
|
}) })]
|
|
375
375
|
})
|
|
376
|
-
}), R > 25 && /* @__PURE__ */
|
|
376
|
+
}), R > 25 && /* @__PURE__ */ m("div", {
|
|
377
377
|
className: "mt-4 flex items-center justify-between text-sm",
|
|
378
|
-
children: [/* @__PURE__ */
|
|
378
|
+
children: [/* @__PURE__ */ m("span", {
|
|
379
379
|
className: "text-text-secondary",
|
|
380
380
|
children: [
|
|
381
381
|
"Showing ",
|
|
@@ -385,14 +385,14 @@ var C = () => {
|
|
|
385
385
|
" of ",
|
|
386
386
|
R
|
|
387
387
|
]
|
|
388
|
-
}), /* @__PURE__ */
|
|
388
|
+
}), /* @__PURE__ */ m("div", {
|
|
389
389
|
className: "flex items-center gap-2",
|
|
390
|
-
children: [/* @__PURE__ */
|
|
390
|
+
children: [/* @__PURE__ */ p("button", {
|
|
391
391
|
disabled: K === 0,
|
|
392
392
|
onClick: () => q((e) => e - 1),
|
|
393
393
|
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",
|
|
394
394
|
children: "Previous"
|
|
395
|
-
}), /* @__PURE__ */
|
|
395
|
+
}), /* @__PURE__ */ p("button", {
|
|
396
396
|
disabled: (K + 1) * 25 >= R,
|
|
397
397
|
onClick: () => q((e) => e + 1),
|
|
398
398
|
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",
|