@burdenoff/microfe-store 2026.731.1 → 2026.805.1
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/StoreAdminRoutes.js +1 -1
- package/dist/StoreAdminRoutes.js.map +1 -1
- package/dist/StoreRoutes.js +1 -1
- package/dist/StoreRoutes.js.map +1 -1
- package/dist/components/InstallAppModal.js +211 -137
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/generated/global-types.d.ts +6 -0
- package/dist/generated/global-types.js.map +1 -1
- package/dist/pages/AdminDashboardPage.js +1 -1
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminReviewModerationPage.js +1 -1
- package/dist/pages/AdminReviewModerationPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js +1 -1
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/AppDetailPage.js +1 -1
- package/dist/pages/AppDetailPage.js.map +1 -1
- package/dist/pages/AppSettingsPage.js +1 -1
- package/dist/pages/AppSettingsPage.js.map +1 -1
- package/dist/pages/CartPage.js +1 -1
- package/dist/pages/CartPage.js.map +1 -1
- package/dist/pages/InstalledAppsPage.js +1 -1
- package/dist/pages/InstalledAppsPage.js.map +1 -1
- package/dist/pages/MarketplaceHomePage.js +1 -1
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/dist/pages/MyLicensesPage.js +1 -1
- package/dist/pages/MyLicensesPage.js.map +1 -1
- package/dist/pages/OrderConfirmationPage.js +1 -1
- package/dist/pages/OrderConfirmationPage.js.map +1 -1
- package/dist/pages/OrdersPage.js +1 -1
- package/dist/pages/OrdersPage.js.map +1 -1
- package/dist/pages/ProductDetailPage.js +1 -1
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CartPage.js","names":[],"sources":["../../src/pages/CartPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useQuotaErrorToast } from '@burdenoff/fe-libs/shared/hooks';\nimport { isQuotaExhaustedError } from '@burdenoff/fe-libs/shared/utils';\nimport {\n ShoppingCart,\n Trash2,\n Plus,\n Minus,\n ArrowLeft,\n ShoppingBag,\n Zap,\n Tag,\n Loader2,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useCart } from '../hooks/useCart';\nimport { formatPrice } from '../utils';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n/**\n * CartPage component\n *\n * Full page cart view with items, summary, and checkout\n */\nexport const CartPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { cartItems, itemCount, total, updateQuantity, removeProduct, clearCart, createOrder } =\n useCart();\n const [isCheckingOut, setIsCheckingOut] = useState(false);\n const [checkoutError, setCheckoutError] = useState<string | null>(null);\n const { t } = useI18n();\n const bus = useEventBus();\n // Surface QUOTA_EXHAUSTED gateway errors as a toast (e.g. when the\n // workspace has hit its store-purchases-per-month limit). The hook also\n // listens for the global `burdenoff:quota-exhausted` event so any other\n // mutation in the page that triggers it will toast as well.\n const { report: reportQuotaError } = useQuotaErrorToast();\n\n // Cart can hold physical (produce) items alongside digital apps. The page\n // compares item.itemType lowercase (see quantity controls below), so match\n // that casing here to avoid claiming physical/mixed orders \"cannot be\n // cancelled\" — those support returns/refunds via the seller's store policy.\n const hasPhysical = cartItems.some((i) => i.itemType === 'physical');\n\n const handleCheckout = async () => {\n try {\n setIsCheckingOut(true);\n setCheckoutError(null);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bus.emit('store.cart.checkout_started' as any, {\n cartId: '',\n itemCount,\n total,\n });\n\n // Build line items from cart\n const lineItems = cartItems.map((item) => ({\n productId: item.productId,\n name: item.product?.displayName || 'Product',\n quantity: item.quantity,\n unitPrice: item.unitPrice,\n subtotal: item.totalPrice,\n itemType: item.itemType.toLowerCase(),\n variantId: item.variantId,\n pricingModel: item.product?.pricingModel || 'PAID_ONETIME',\n }));\n\n // Create order input\n const orderInput = {\n total,\n lineItems,\n };\n\n // Call createStoreOrder mutation to create PENDING order\n const order = await createOrder(orderInput);\n\n if (order?.id) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bus.emit('store.order.placed' as any, {\n orderId: order.id,\n total,\n itemCount,\n });\n // Clear cart now that the order is created — non-blocking so a\n // clear failure never prevents the user from reaching billing.\n clearCart().catch(() => {});\n // Navigate to billing page with the order ID\n navigate(`/billing/checkout/store/${order.id}`);\n } else {\n setCheckoutError(\n t('pages.cart.failedToCreate', {\n defaultValue: 'Failed to create order. Please try again.',\n })\n );\n setIsCheckingOut(false);\n }\n } catch (error) {\n console.error('Checkout error:', error);\n // Quota-exhausted errors surface a global toast via the shell's\n // GlobalUiProvider; suppress the inline checkout error so we don't\n // stack two error UIs on top of each other.\n if (isQuotaExhaustedError(error)) {\n reportQuotaError(error);\n setIsCheckingOut(false);\n return;\n }\n setCheckoutError(\n error instanceof Error ? error.message : 'An error occurred during checkout'\n );\n setIsCheckingOut(false);\n }\n };\n\n const handleContinueShopping = () => {\n navigate(`${basePath}/marketplace`);\n };\n\n return (\n <div className=\"h-full overflow-y-auto\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto max-w-6xl\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div className=\"flex items-center gap-3\">\n <ShoppingCart className=\"size-6 text-text-primary\" />\n <h1 className=\"text-xl font-bold text-text-primary\">\n {t('cart.title', { defaultValue: 'Shopping Cart' })}\n </h1>\n {itemCount > 0 && (\n <span className=\"rounded-full bg-action-primary-bg px-2.5 py-0.5 text-sm font-medium text-action-primary-text\">\n {itemCount}{' '}\n {itemCount === 1\n ? t('pages.cart.item', { defaultValue: `${itemCount} item`, count: itemCount })\n : t('pages.cart.items', {\n defaultValue: `${itemCount} items`,\n count: itemCount,\n })}\n </span>\n )}\n </div>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl p-6\">\n {cartItems.length === 0 ? (\n // Empty Cart State\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={t('cart.empty', { defaultValue: 'Your cart is empty' })}\n description={t('pages.cart.emptyDescription', {\n defaultValue:\n 'Looks like you have not added any apps or products to your cart yet. Start exploring the marketplace to find something you will love!',\n })}\n action={\n <button\n type=\"button\"\n onClick={handleContinueShopping}\n className=\"cursor-pointer flex items-center gap-2 rounded-lg bg-action-primary-bg px-6 py-3 font-semibold text-action-primary-text transition-opacity hover:opacity-90\"\n >\n <ShoppingBag className=\"size-5\" />\n {t('pages.cart.browseMarketplace', { defaultValue: 'Browse Marketplace' })}\n </button>\n }\n />\n ) : (\n // Cart with Items\n <div className=\"mx-auto max-w-3xl\">\n <PagePurpose className=\"mb-6\">\n Review the apps and products you're about to buy, adjust quantities for physical\n items, and confirm the total before checkout. Nothing is charged until you proceed —\n once you do, your purchases become licenses you can install into a workspace.\n </PagePurpose>\n {/* Cart Items */}\n <div>\n {/* Cart Actions */}\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('pages.cart.cartItems', {\n defaultValue: `Cart Items (${itemCount})`,\n count: itemCount,\n })}\n </h2>\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"cursor-pointer flex items-center gap-1 text-sm text-text-muted transition-colors hover:text-status-error-text\"\n >\n <Trash2 className=\"size-4\" />\n {t('cart.clearCart', { defaultValue: 'Clear Cart' })}\n </button>\n </div>\n\n {/* Items List */}\n <ul className=\"space-y-4\">\n {cartItems.map((item) => (\n <li\n key={item.id}\n className=\"flex gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-[var(--shadow-pop)]\"\n >\n {/* Product Image */}\n <div className=\"size-24 flex-shrink-0 overflow-hidden rounded-lg bg-bg-sunken\">\n {item.product?.iconUrl ? (\n <img\n src={item.product.iconUrl}\n alt={item.product.displayName}\n className=\"size-full object-cover\"\n />\n ) : (\n <div className=\"flex size-full items-center justify-center text-2xl font-bold text-text-muted\">\n {item.product?.displayName?.charAt(0) || 'P'}\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"flex flex-1 flex-col\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {item.product?.displayName || 'Product'}\n </h3>\n <p className=\"text-sm text-text-muted\">\n {item.product?.publisher?.displayName || 'Unknown Publisher'}\n </p>\n {item.variant && (\n <p className=\"mt-1 text-xs text-text-muted\">\n Version {item.variant.version}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => removeProduct(item.id)}\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-status-error-bg-subtle/10 hover:text-status-error-text\"\n aria-label={`Remove ${item.product?.displayName ?? 'item'} from cart`}\n title=\"Remove item\"\n >\n <Trash2 className=\"size-5\" />\n </button>\n </div>\n\n {/* Item Type Badge */}\n <div className=\"mt-2 flex items-center gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-muted\">\n {item.itemType === 'digital' ? (\n <>\n <Zap className=\"size-3\" />\n {t('pages.cart.digital', { defaultValue: 'Digital' })}\n </>\n ) : (\n <>\n <Tag className=\"size-3\" />\n {t('pages.cart.physical', { defaultValue: 'Physical' })}\n </>\n )}\n </span>\n {item.product?.pricingModel === 'SUBSCRIPTION' && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-accent/10 px-2 py-0.5 text-xs font-medium text-text-link\">\n {t('store.pricingModels.subscription', {\n defaultValue: 'Subscription',\n })}\n </span>\n )}\n </div>\n\n {/* Quantity and Price Row */}\n <div className=\"mt-auto flex items-center justify-between pt-3\">\n {/* Quantity Controls - Only show for physical items */}\n {item.itemType === 'physical' ? (\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm text-text-muted\">Qty:</span>\n <div className=\"flex items-center rounded-lg border border-border-default\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(item.id, item.quantity - 1)}\n className=\"cursor-pointer rounded-l-lg p-2 transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={item.quantity <= 1}\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"min-w-[3rem] text-center font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(item.id, item.quantity + 1)}\n className=\"cursor-pointer rounded-r-lg p-2 transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n </div>\n ) : (\n <div className=\"text-sm text-text-muted\">\n {t('pages.cart.singleLicense', { defaultValue: 'Single license' })}\n </div>\n )}\n\n {/* Price */}\n <div className=\"text-right\">\n <p className=\"text-lg font-bold text-text-primary\">\n {formatPrice(item.totalPrice)}\n </p>\n {item.quantity > 1 && item.itemType === 'physical' && (\n <p className=\"text-xs text-text-muted\">\n {formatPrice(item.unitPrice)} each\n </p>\n )}\n </div>\n </div>\n </div>\n </li>\n ))}\n </ul>\n\n {/* Checkout Footer */}\n <EmphasisPanel className=\"mt-8\">\n {checkoutError && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {checkoutError}\n </div>\n )}\n <p className=\"mb-3 text-xs text-text-muted\">\n {hasPhysical\n ? 'Physical orders ship to your delivery address. Returns and refunds are handled by the seller per their store policy.'\n : 'All digital app purchases are final and non-refundable. Once an order is completed it cannot be cancelled or reversed.'}\n </p>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm text-text-muted\">Total</p>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatPrice(total)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleCheckout}\n disabled={isCheckingOut}\n className=\"flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-6 py-3 font-semibold text-action-primary-text transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-70\"\n >\n {isCheckingOut ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n {t('pages.cart.processing', { defaultValue: 'Processing...' })}\n </>\n ) : (\n <>\n <Zap className=\"size-4\" />\n {t('pages.cart.proceedToCheckout', { defaultValue: 'Proceed to Checkout' })}\n </>\n )}\n </button>\n </div>\n </EmphasisPanel>\n\n {/* Continue Shopping */}\n <button\n type=\"button\"\n onClick={handleContinueShopping}\n className=\"cursor-pointer mt-4 flex items-center gap-2 text-sm font-medium text-text-link hover:underline\"\n >\n <ArrowLeft className=\"size-4\" />\n {t('cart.continueShopping', { defaultValue: 'Continue Shopping' })}\n </button>\n </div>\n </div>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CartPage.js","names":[],"sources":["../../src/pages/CartPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useQuotaErrorToast } from '@burdenoff/fe-libs/shared/hooks';\nimport { isQuotaExhaustedError } from '@burdenoff/fe-libs/shared/utils';\nimport {\n ShoppingCart,\n Trash2,\n Plus,\n Minus,\n ArrowLeft,\n ShoppingBag,\n Zap,\n Tag,\n Loader2,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { useCart } from '../hooks/useCart';\nimport { formatPrice } from '../utils';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n/**\n * CartPage component\n *\n * Full page cart view with items, summary, and checkout\n */\nexport const CartPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { cartItems, itemCount, total, updateQuantity, removeProduct, clearCart, createOrder } =\n useCart();\n const [isCheckingOut, setIsCheckingOut] = useState(false);\n const [checkoutError, setCheckoutError] = useState<string | null>(null);\n const { t } = useI18n();\n const bus = useEventBus();\n // Surface QUOTA_EXHAUSTED gateway errors as a toast (e.g. when the\n // workspace has hit its store-purchases-per-month limit). The hook also\n // listens for the global `burdenoff:quota-exhausted` event so any other\n // mutation in the page that triggers it will toast as well.\n const { report: reportQuotaError } = useQuotaErrorToast();\n\n // Cart can hold physical (produce) items alongside digital apps. The page\n // compares item.itemType lowercase (see quantity controls below), so match\n // that casing here to avoid claiming physical/mixed orders \"cannot be\n // cancelled\" — those support returns/refunds via the seller's store policy.\n const hasPhysical = cartItems.some((i) => i.itemType === 'physical');\n\n const handleCheckout = async () => {\n try {\n setIsCheckingOut(true);\n setCheckoutError(null);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bus.emit('store.cart.checkout_started' as any, {\n cartId: '',\n itemCount,\n total,\n });\n\n // Build line items from cart\n const lineItems = cartItems.map((item) => ({\n productId: item.productId,\n name: item.product?.displayName || 'Product',\n quantity: item.quantity,\n unitPrice: item.unitPrice,\n subtotal: item.totalPrice,\n itemType: item.itemType.toLowerCase(),\n variantId: item.variantId,\n pricingModel: item.product?.pricingModel || 'PAID_ONETIME',\n }));\n\n // Create order input\n const orderInput = {\n total,\n lineItems,\n };\n\n // Call createStoreOrder mutation to create PENDING order\n const order = await createOrder(orderInput);\n\n if (order?.id) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n bus.emit('store.order.placed' as any, {\n orderId: order.id,\n total,\n itemCount,\n });\n // Clear cart now that the order is created — non-blocking so a\n // clear failure never prevents the user from reaching billing.\n clearCart().catch(() => {});\n // Navigate to billing page with the order ID\n navigate(`/billing/checkout/store/${order.id}`);\n } else {\n setCheckoutError(\n t('pages.cart.failedToCreate', {\n defaultValue: 'Failed to create order. Please try again.',\n })\n );\n setIsCheckingOut(false);\n }\n } catch (error) {\n console.error('Checkout error:', error);\n // Quota-exhausted errors surface a global toast via the shell's\n // GlobalUiProvider; suppress the inline checkout error so we don't\n // stack two error UIs on top of each other.\n if (isQuotaExhaustedError(error)) {\n reportQuotaError(error);\n setIsCheckingOut(false);\n return;\n }\n setCheckoutError(\n error instanceof Error ? error.message : 'An error occurred during checkout'\n );\n setIsCheckingOut(false);\n }\n };\n\n const handleContinueShopping = () => {\n navigate(`${basePath}/marketplace`);\n };\n\n return (\n <div className=\"h-full overflow-y-auto\">\n {/* Header */}\n <header className=\"sticky top-0 z-10 border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"mx-auto max-w-6xl\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n aria-label=\"Go back\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div className=\"flex items-center gap-3\">\n <ShoppingCart className=\"size-6 text-text-primary\" />\n <h1 className=\"text-xl font-bold text-text-primary\">\n {t('cart.title', { defaultValue: 'Shopping Cart' })}\n </h1>\n {itemCount > 0 && (\n <span className=\"rounded-full bg-action-primary-bg px-2.5 py-0.5 text-sm font-medium text-action-primary-text\">\n {itemCount}{' '}\n {itemCount === 1\n ? t('pages.cart.item', { defaultValue: `${itemCount} item`, count: itemCount })\n : t('pages.cart.items', {\n defaultValue: `${itemCount} items`,\n count: itemCount,\n })}\n </span>\n )}\n </div>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-6xl p-6\">\n {cartItems.length === 0 ? (\n // Empty Cart State\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={t('cart.empty', { defaultValue: 'Your cart is empty' })}\n description={t('pages.cart.emptyDescription', {\n defaultValue:\n 'Looks like you have not added any apps or products to your cart yet. Start exploring the marketplace to find something you will love!',\n })}\n action={\n <button\n type=\"button\"\n onClick={handleContinueShopping}\n className=\"cursor-pointer flex items-center gap-2 rounded-lg bg-action-primary-bg px-6 py-3 font-semibold text-action-primary-text transition-opacity hover:opacity-90\"\n >\n <ShoppingBag className=\"size-5\" />\n {t('pages.cart.browseMarketplace', { defaultValue: 'Browse Marketplace' })}\n </button>\n }\n />\n ) : (\n // Cart with Items\n <div className=\"mx-auto max-w-3xl\">\n <PagePurpose className=\"mb-6\">\n Review the apps and products you're about to buy, adjust quantities for physical\n items, and confirm the total before checkout. Nothing is charged until you proceed —\n once you do, your purchases become licenses you can install into a workspace.\n </PagePurpose>\n {/* Cart Items */}\n <div>\n {/* Cart Actions */}\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('pages.cart.cartItems', {\n defaultValue: `Cart Items (${itemCount})`,\n count: itemCount,\n })}\n </h2>\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"cursor-pointer flex items-center gap-1 text-sm text-text-muted transition-colors hover:text-status-error-text\"\n >\n <Trash2 className=\"size-4\" />\n {t('cart.clearCart', { defaultValue: 'Clear Cart' })}\n </button>\n </div>\n\n {/* Items List */}\n <ul className=\"space-y-4\">\n {cartItems.map((item) => (\n <li\n key={item.id}\n className=\"flex gap-4 rounded-xl border border-border-seam bg-bg-surface p-4 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-[var(--shadow-pop)]\"\n >\n {/* Product Image */}\n <div className=\"size-24 flex-shrink-0 overflow-hidden rounded-lg bg-bg-sunken\">\n {item.product?.iconUrl ? (\n <img\n src={item.product.iconUrl}\n alt={item.product.displayName}\n className=\"size-full object-cover\"\n />\n ) : (\n <div className=\"flex size-full items-center justify-center text-2xl font-bold text-text-muted\">\n {item.product?.displayName?.charAt(0) || 'P'}\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"flex flex-1 flex-col\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {item.product?.displayName || 'Product'}\n </h3>\n <p className=\"text-sm text-text-muted\">\n {item.product?.publisher?.displayName || 'Unknown Publisher'}\n </p>\n {item.variant && (\n <p className=\"mt-1 text-xs text-text-muted\">\n Version {item.variant.version}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => removeProduct(item.id)}\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-status-error-bg-subtle/10 hover:text-status-error-text\"\n aria-label={`Remove ${item.product?.displayName ?? 'item'} from cart`}\n title=\"Remove item\"\n >\n <Trash2 className=\"size-5\" />\n </button>\n </div>\n\n {/* Item Type Badge */}\n <div className=\"mt-2 flex items-center gap-2\">\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-muted\">\n {item.itemType === 'digital' ? (\n <>\n <Zap className=\"size-3\" />\n {t('pages.cart.digital', { defaultValue: 'Digital' })}\n </>\n ) : (\n <>\n <Tag className=\"size-3\" />\n {t('pages.cart.physical', { defaultValue: 'Physical' })}\n </>\n )}\n </span>\n {item.product?.pricingModel === 'SUBSCRIPTION' && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-accent/10 px-2 py-0.5 text-xs font-medium text-text-link\">\n {t('store.pricingModels.subscription', {\n defaultValue: 'Subscription',\n })}\n </span>\n )}\n </div>\n\n {/* Quantity and Price Row */}\n <div className=\"mt-auto flex items-center justify-between pt-3\">\n {/* Quantity Controls - Only show for physical items */}\n {item.itemType === 'physical' ? (\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm text-text-muted\">Qty:</span>\n <div className=\"flex items-center rounded-lg border border-border-default\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(item.id, item.quantity - 1)}\n className=\"cursor-pointer rounded-l-lg p-2 transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={item.quantity <= 1}\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"min-w-[3rem] text-center font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(item.id, item.quantity + 1)}\n className=\"cursor-pointer rounded-r-lg p-2 transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n </div>\n ) : (\n <div className=\"text-sm text-text-muted\">\n {t('pages.cart.singleLicense', { defaultValue: 'Single license' })}\n </div>\n )}\n\n {/* Price */}\n <div className=\"text-right\">\n <p className=\"text-lg font-bold text-text-primary\">\n {formatPrice(item.totalPrice)}\n </p>\n {item.quantity > 1 && item.itemType === 'physical' && (\n <p className=\"text-xs text-text-muted\">\n {formatPrice(item.unitPrice)} each\n </p>\n )}\n </div>\n </div>\n </div>\n </li>\n ))}\n </ul>\n\n {/* Checkout Footer */}\n <EmphasisPanel className=\"mt-8\">\n {checkoutError && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {checkoutError}\n </div>\n )}\n <p className=\"mb-3 text-xs text-text-muted\">\n {hasPhysical\n ? 'Physical orders ship to your delivery address. Returns and refunds are handled by the seller per their store policy.'\n : 'All digital app purchases are final and non-refundable. Once an order is completed it cannot be cancelled or reversed.'}\n </p>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm text-text-muted\">Total</p>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatPrice(total)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleCheckout}\n disabled={isCheckingOut}\n className=\"flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-6 py-3 font-semibold text-action-primary-text transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-70\"\n >\n {isCheckingOut ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n {t('pages.cart.processing', { defaultValue: 'Processing...' })}\n </>\n ) : (\n <>\n <Zap className=\"size-4\" />\n {t('pages.cart.proceedToCheckout', { defaultValue: 'Proceed to Checkout' })}\n </>\n )}\n </button>\n </div>\n </EmphasisPanel>\n\n {/* Continue Shopping */}\n <button\n type=\"button\"\n onClick={handleContinueShopping}\n className=\"cursor-pointer mt-4 flex items-center gap-2 text-sm font-medium text-text-link hover:underline\"\n >\n <ArrowLeft className=\"size-4\" />\n {t('cart.continueShopping', { defaultValue: 'Continue Shopping' })}\n </button>\n </div>\n </div>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { useI18n as ee } from "@burdenoff/fe-libs/shared/providers/shell/I18nPro
|
|
|
7
7
|
import { AlertCircle as te, ChevronLeft as ne, ChevronRight as s, ExternalLink as c, Loader2 as l, Plus as u, RefreshCw as d, Search as f, Settings as re, Trash2 as p } from "lucide-react";
|
|
8
8
|
import { useApolloClient as ie } from "@apollo/client/react";
|
|
9
9
|
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
10
|
-
import { useNavigate as ae } from "react-router
|
|
10
|
+
import { useNavigate as ae } from "react-router";
|
|
11
11
|
import { gql as _ } from "@apollo/client";
|
|
12
12
|
import { AlertDialog as v, AlertDialogAction as oe, AlertDialogCancel as y, AlertDialogContent as b, AlertDialogDescription as x, AlertDialogFooter as S, AlertDialogHeader as C, AlertDialogTitle as se, IllustratedEmptyState as ce, PagePurpose as le } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
//#region src/pages/InstalledAppsPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstalledAppsPage.js","names":[],"sources":["../../src/pages/InstalledAppsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Package,\n Plus,\n RefreshCw,\n Trash2,\n AlertCircle,\n Loader2,\n Search,\n ChevronLeft,\n ChevronRight,\n ExternalLink,\n Settings,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { type InstalledApp, useInstallations } from '../hooks/useInstallations';\nimport { StoreInstallationStatus } from '../generated/global-types';\nimport type { InstallationFilterInput } from '../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AlertDialog,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogCancel,\n AlertDialogAction,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useInstallationStatusUpdatedSubscription } from '../generated/global-operations';\nimport { useApolloClient } from '@apollo/client/react';\nimport { gql } from '@apollo/client';\n\nconst PAGE_SIZE = 20;\n\nconst STATUS_FILTERS: Array<{ label: string; value: StoreInstallationStatus | 'all' }> = [\n { label: 'All', value: 'all' },\n { label: 'Active', value: StoreInstallationStatus.Active },\n { label: 'Inactive', value: StoreInstallationStatus.Inactive },\n { label: 'Pending', value: StoreInstallationStatus.Pending },\n { label: 'Installing', value: StoreInstallationStatus.Installing },\n { label: 'Failed', value: StoreInstallationStatus.Failed },\n { label: 'Uninstalled', value: StoreInstallationStatus.Uninstalled },\n];\n\nconst statusBadgeClass = (status: StoreInstallationStatus): string => {\n if (status === StoreInstallationStatus.Active) {\n return 'bg-status-success-bg-subtle text-status-success-text';\n }\n if (\n status === StoreInstallationStatus.Pending ||\n status === StoreInstallationStatus.Installing ||\n status === StoreInstallationStatus.Inactive\n ) {\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n }\n if (status === StoreInstallationStatus.Failed) {\n return 'bg-status-error-bg-subtle text-status-error-text';\n }\n return 'bg-bg-sunken text-text-muted';\n};\n\nfunction getAppName(installation: InstalledApp): string {\n if (installation.productName) return installation.productName;\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.name && typeof manifest.name === 'string') return manifest.name;\n if (manifest?.slug && typeof manifest.slug === 'string') {\n return manifest.slug\n .split('-')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n }\n return installation.productId.slice(0, 8) + '…';\n}\n\nfunction getAppIcon(installation: InstalledApp): string | null {\n if (installation.productIcon) return installation.productIcon;\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.icon && typeof manifest.icon === 'string') return manifest.icon;\n return null;\n}\n\nfunction getAppType(installation: InstalledApp): string | null {\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.type && typeof manifest.type === 'string') return manifest.type;\n return null;\n}\n\nfunction getInstallationId(installation: InstalledApp): string | null {\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.installationId && typeof manifest.installationId === 'string')\n return manifest.installationId;\n return null;\n}\n\nfunction AppAvatar({ installation }: { installation: InstalledApp }) {\n const icon = getAppIcon(installation);\n const name = getAppName(installation);\n const [imgFailed, setImgFailed] = useState(false);\n\n if (icon && !imgFailed) {\n return (\n <img\n src={icon}\n alt={name}\n onError={() => setImgFailed(true)}\n className=\"size-8 rounded object-contain\"\n />\n );\n }\n return (\n <div className=\"flex size-8 items-center justify-center rounded bg-bg-sunken text-sm font-semibold text-text-muted\">\n {name.charAt(0).toUpperCase()}\n </div>\n );\n}\n\nconst UNINSTALL_APP_BY_APPLICATION = gql`\n mutation UninstallAppByApplicationFromStore(\n $applicationId: ID!\n $workspaceId: ID!\n $reason: String\n ) {\n uninstallAppByApplication(\n applicationId: $applicationId\n workspaceId: $workspaceId\n reason: $reason\n )\n }\n`;\n\nexport const InstalledAppsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath, workspaceId } = useStore();\n const { t } = useI18n();\n const apolloClient = useApolloClient();\n\n const [selectedStatus, setSelectedStatus] = useState<StoreInstallationStatus | 'all'>('all');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(1);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [pendingUninstall, setPendingUninstall] = useState<InstalledApp | null>(null);\n\n const statusFilter: InstallationFilterInput | undefined = useMemo(\n () => (selectedStatus === 'all' ? undefined : { status: selectedStatus }),\n [selectedStatus]\n );\n\n const {\n installations,\n totalCount,\n hasMore,\n loading,\n error,\n uninstall,\n uninstallingIds,\n refetch,\n } = useInstallations({ filter: statusFilter, limit: PAGE_SIZE, offset: (page - 1) * PAGE_SIZE });\n\n useInstallationStatusUpdatedSubscription({\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n onData: () => {\n void refetch();\n },\n onError: (err) => {\n console.warn('InstallationStatusUpdated subscription error:', err.message);\n },\n });\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n // Client-side search filter over the current page\n const filteredInstallations = useMemo(() => {\n if (!search.trim()) return installations;\n const term = search.toLowerCase();\n return installations.filter((inst) => {\n const name = getAppName(inst).toLowerCase();\n const ws = (inst.workspaceName ?? inst.workspaceId).toLowerCase();\n const type = (getAppType(inst) ?? '').toLowerCase();\n return name.includes(term) || ws.includes(term) || type.includes(term);\n });\n }, [installations, search]);\n\n const sortedInstallations = useMemo(\n () => [...filteredInstallations].sort((a, b) => b.installedAt.localeCompare(a.installedAt)),\n [filteredInstallations]\n );\n\n const handleStatusChange = useCallback((status: StoreInstallationStatus | 'all') => {\n setSelectedStatus(status);\n setPage(1);\n }, []);\n\n const handleUninstall = async (installation: InstalledApp) => {\n setErrorMessage(null);\n const ok = await uninstall(installation, 'User requested uninstall from store UI');\n if (!ok) {\n setErrorMessage('Could not uninstall. Try again.');\n } else {\n apolloClient\n .mutate({\n mutation: UNINSTALL_APP_BY_APPLICATION,\n variables: {\n applicationId: installation.productId,\n workspaceId: installation.workspaceId,\n reason: 'User uninstalled from Installed Apps',\n },\n })\n .catch(() => {});\n }\n setPendingUninstall(null);\n };\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.installed.title', { defaultValue: 'Installed Apps' })}\n </h1>\n <p className=\"text-sm text-text-muted\">Manage your installed applications</p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 font-semibold text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"size-5\" />\n <span>{t('pages.installed.installNew', { defaultValue: 'Install New App' })}</span>\n </button>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-7xl\">\n <PagePurpose className=\"mb-4\">\n Every app you install into a workspace lives here. Check status, jump into details, or\n remove apps a workspace no longer needs.\n </PagePurpose>\n\n {/* Toolbar: status filters + search + refresh */}\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n {STATUS_FILTERS.map((f) => (\n <button\n type=\"button\"\n key={f.label}\n onClick={() => handleStatusChange(f.value)}\n className={`cursor-pointer rounded-full px-3 py-1.5 text-sm font-medium transition-colors ${\n selectedStatus === f.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-surface'\n }`}\n >\n {f.label}\n </button>\n ))}\n\n <div className=\"relative ml-auto\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-muted\" />\n <input\n type=\"search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name, workspace…\"\n className=\"h-9 rounded-lg border border-border-default bg-bg-surface pl-9 pr-3 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {errorMessage}\n </div>\n )}\n\n {loading ? (\n <output\n aria-label=\"Loading installed apps\"\n className=\"flex items-center justify-center py-16\"\n >\n <Loader2 className=\"size-8 animate-spin text-text-muted\" aria-hidden=\"true\" />\n </output>\n ) : error ? (\n <div\n role=\"alert\"\n className=\"flex flex-col items-center justify-center rounded-xl border border-status-error-border bg-status-error-bg-subtle py-16 text-center\"\n >\n <AlertCircle className=\"mb-3 size-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n Failed to load installations\n </h3>\n <p className=\"mt-2 text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"mt-4 cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n Try Again\n </button>\n </div>\n ) : sortedInstallations.length === 0 && totalCount === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={selectedStatus === 'all' ? 'No apps installed' : 'No apps match this filter'}\n description={\n selectedStatus === 'all'\n ? 'Browse the marketplace to install your first app.'\n : 'Try a different status filter, or browse the marketplace.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 font-semibold text-action-primary-text transition-colors hover:opacity-90\"\n >\n Browse Marketplace\n </button>\n }\n />\n ) : (\n <>\n <div className=\"mb-2 flex items-center justify-between text-sm text-text-muted\">\n <span aria-live=\"polite\">\n {search.trim()\n ? `${sortedInstallations.length} of ${totalCount} shown`\n : `${totalCount} installation${totalCount === 1 ? '' : 's'}`}\n </span>\n <span>\n Page {page} of {totalPages}\n </span>\n </div>\n\n {/* Table */}\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full min-w-[720px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left text-xs font-semibold uppercase tracking-wide text-text-muted\">\n <th className=\"px-4 py-3\">App</th>\n <th className=\"px-4 py-3\">Workspace</th>\n <th className=\"px-4 py-3\">Status</th>\n <th className=\"px-4 py-3\">Version</th>\n <th className=\"px-4 py-3\">Installed</th>\n <th className=\"px-4 py-3 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam bg-bg-surface\">\n {sortedInstallations.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"py-12 text-center text-text-muted\">\n No results match your search.\n </td>\n </tr>\n ) : (\n sortedInstallations.map((installation) => {\n const appName = getAppName(installation);\n const appType = getAppType(installation);\n const installationId = getInstallationId(installation);\n const uninstalling = uninstallingIds.has(\n `${installation.workspaceId}:${installation.productId}`\n );\n const isIntegration =\n appType === 'INTEGRATION' || appType === 'API_INTEGRATION';\n\n return (\n <tr\n key={installation.id}\n className=\"transition-colors hover:bg-bg-subtle\"\n >\n {/* App column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <AppAvatar installation={installation} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {appName}\n </p>\n {appType && (\n <span className=\"mt-0.5 inline-block rounded bg-bg-sunken px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {appType}\n </span>\n )}\n {isIntegration && installationId && (\n <p\n className=\"truncate text-[10px] text-text-muted\"\n title={installationId}\n >\n ID: {installationId.slice(0, 16)}…\n </p>\n )}\n </div>\n </div>\n </td>\n\n {/* Workspace column */}\n <td className=\"px-4 py-3\">\n <p className=\"truncate text-text-primary\">\n {installation.workspaceName ?? installation.workspaceId}\n </p>\n {installation.workspaceType && (\n <p className=\"text-xs text-text-muted\">\n {installation.workspaceType}\n </p>\n )}\n </td>\n\n {/* Status column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <span\n className={`inline-block rounded-full px-2 py-1 text-xs font-medium ${statusBadgeClass(installation.status)}`}\n >\n {installation.status}\n </span>\n {installation.productStatus === 'UNLISTED' && (\n <span className=\"inline-block rounded-full bg-status-warning-bg-subtle px-2 py-1 text-xs font-medium text-status-warning-text\">\n Delisted\n </span>\n )}\n </div>\n </td>\n\n {/* Version column */}\n <td className=\"px-4 py-3 font-variant-numeric tabular-nums text-text-muted\">\n {installation.version ? `v${installation.version}` : '—'}\n </td>\n\n {/* Installed date column */}\n <td className=\"px-4 py-3 text-text-muted\">\n {new Date(installation.installedAt).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })}\n </td>\n\n {/* Actions column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-2\">\n {isIntegration ? (\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n title=\"View installation details\"\n >\n <ExternalLink className=\"size-3.5\" />\n Details\n </button>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Details\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n `${basePath}/installed/${installation.id}/settings`\n )\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Settings className=\"size-3.5\" />\n Configure\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n `${basePath}/marketplace/product/${installation.productId}`\n )\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Product\n </button>\n {installation.status !== StoreInstallationStatus.Uninstalled && (\n <button\n type=\"button\"\n onClick={() => setPendingUninstall(installation)}\n disabled={uninstalling}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-status-error-border px-3 py-1.5 text-xs font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {uninstalling ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Uninstall\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n\n {/* Pagination controls */}\n {totalPages > 1 && (\n <div className=\"mt-4 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page === 1 || loading}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: Math.min(totalPages, 7) }, (_, i) => {\n const p =\n totalPages <= 7\n ? i + 1\n : i < 3\n ? i + 1\n : i >= 4\n ? totalPages - (6 - i)\n : page;\n return (\n <button\n key={p}\n type=\"button\"\n onClick={() => setPage(p)}\n className={`size-8 rounded-lg text-sm font-medium transition-colors ${\n p === page\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-muted hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => setPage((p) => (hasMore ? p + 1 : Math.min(totalPages, p + 1)))}\n disabled={page >= totalPages || loading}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </div>\n </main>\n\n <AlertDialog\n open={pendingUninstall !== null}\n onOpenChange={(open) => !open && setPendingUninstall(null)}\n >\n <AlertDialogContent className=\"max-w-md\">\n <AlertDialogHeader>\n <AlertDialogTitle>Uninstall app</AlertDialogTitle>\n <AlertDialogDescription>\n {pendingUninstall && (\n <>\n Remove{' '}\n <span className=\"font-medium text-text-primary\">\n {getAppName(pendingUninstall)}\n </span>{' '}\n from{' '}\n <span className=\"font-medium text-text-primary\">\n {pendingUninstall.workspaceName ?? pendingUninstall.workspaceId}\n </span>\n ?{' '}\n {(getAppType(pendingUninstall) === 'INTEGRATION' ||\n getAppType(pendingUninstall) === 'API_INTEGRATION') && (\n <span className=\"mt-1 block text-xs text-text-muted\">\n This will also revoke the integration's access on the third-party side.\n </span>\n )}\n </>\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel onClick={() => setPendingUninstall(null)}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-status-error-bg text-status-error-text hover:bg-status-error-bg-emphasis\"\n onClick={() => pendingUninstall && handleUninstall(pendingUninstall)}\n >\n Uninstall\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InstalledAppsPage.js","names":[],"sources":["../../src/pages/InstalledAppsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState, useCallback } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n Package,\n Plus,\n RefreshCw,\n Trash2,\n AlertCircle,\n Loader2,\n Search,\n ChevronLeft,\n ChevronRight,\n ExternalLink,\n Settings,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { type InstalledApp, useInstallations } from '../hooks/useInstallations';\nimport { StoreInstallationStatus } from '../generated/global-types';\nimport type { InstallationFilterInput } from '../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AlertDialog,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogCancel,\n AlertDialogAction,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useInstallationStatusUpdatedSubscription } from '../generated/global-operations';\nimport { useApolloClient } from '@apollo/client/react';\nimport { gql } from '@apollo/client';\n\nconst PAGE_SIZE = 20;\n\nconst STATUS_FILTERS: Array<{ label: string; value: StoreInstallationStatus | 'all' }> = [\n { label: 'All', value: 'all' },\n { label: 'Active', value: StoreInstallationStatus.Active },\n { label: 'Inactive', value: StoreInstallationStatus.Inactive },\n { label: 'Pending', value: StoreInstallationStatus.Pending },\n { label: 'Installing', value: StoreInstallationStatus.Installing },\n { label: 'Failed', value: StoreInstallationStatus.Failed },\n { label: 'Uninstalled', value: StoreInstallationStatus.Uninstalled },\n];\n\nconst statusBadgeClass = (status: StoreInstallationStatus): string => {\n if (status === StoreInstallationStatus.Active) {\n return 'bg-status-success-bg-subtle text-status-success-text';\n }\n if (\n status === StoreInstallationStatus.Pending ||\n status === StoreInstallationStatus.Installing ||\n status === StoreInstallationStatus.Inactive\n ) {\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n }\n if (status === StoreInstallationStatus.Failed) {\n return 'bg-status-error-bg-subtle text-status-error-text';\n }\n return 'bg-bg-sunken text-text-muted';\n};\n\nfunction getAppName(installation: InstalledApp): string {\n if (installation.productName) return installation.productName;\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.name && typeof manifest.name === 'string') return manifest.name;\n if (manifest?.slug && typeof manifest.slug === 'string') {\n return manifest.slug\n .split('-')\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n }\n return installation.productId.slice(0, 8) + '…';\n}\n\nfunction getAppIcon(installation: InstalledApp): string | null {\n if (installation.productIcon) return installation.productIcon;\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.icon && typeof manifest.icon === 'string') return manifest.icon;\n return null;\n}\n\nfunction getAppType(installation: InstalledApp): string | null {\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.type && typeof manifest.type === 'string') return manifest.type;\n return null;\n}\n\nfunction getInstallationId(installation: InstalledApp): string | null {\n const manifest = installation.manifest as Record<string, unknown> | null;\n if (manifest?.installationId && typeof manifest.installationId === 'string')\n return manifest.installationId;\n return null;\n}\n\nfunction AppAvatar({ installation }: { installation: InstalledApp }) {\n const icon = getAppIcon(installation);\n const name = getAppName(installation);\n const [imgFailed, setImgFailed] = useState(false);\n\n if (icon && !imgFailed) {\n return (\n <img\n src={icon}\n alt={name}\n onError={() => setImgFailed(true)}\n className=\"size-8 rounded object-contain\"\n />\n );\n }\n return (\n <div className=\"flex size-8 items-center justify-center rounded bg-bg-sunken text-sm font-semibold text-text-muted\">\n {name.charAt(0).toUpperCase()}\n </div>\n );\n}\n\nconst UNINSTALL_APP_BY_APPLICATION = gql`\n mutation UninstallAppByApplicationFromStore(\n $applicationId: ID!\n $workspaceId: ID!\n $reason: String\n ) {\n uninstallAppByApplication(\n applicationId: $applicationId\n workspaceId: $workspaceId\n reason: $reason\n )\n }\n`;\n\nexport const InstalledAppsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath, workspaceId } = useStore();\n const { t } = useI18n();\n const apolloClient = useApolloClient();\n\n const [selectedStatus, setSelectedStatus] = useState<StoreInstallationStatus | 'all'>('all');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(1);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [pendingUninstall, setPendingUninstall] = useState<InstalledApp | null>(null);\n\n const statusFilter: InstallationFilterInput | undefined = useMemo(\n () => (selectedStatus === 'all' ? undefined : { status: selectedStatus }),\n [selectedStatus]\n );\n\n const {\n installations,\n totalCount,\n hasMore,\n loading,\n error,\n uninstall,\n uninstallingIds,\n refetch,\n } = useInstallations({ filter: statusFilter, limit: PAGE_SIZE, offset: (page - 1) * PAGE_SIZE });\n\n useInstallationStatusUpdatedSubscription({\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n onData: () => {\n void refetch();\n },\n onError: (err) => {\n console.warn('InstallationStatusUpdated subscription error:', err.message);\n },\n });\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n // Client-side search filter over the current page\n const filteredInstallations = useMemo(() => {\n if (!search.trim()) return installations;\n const term = search.toLowerCase();\n return installations.filter((inst) => {\n const name = getAppName(inst).toLowerCase();\n const ws = (inst.workspaceName ?? inst.workspaceId).toLowerCase();\n const type = (getAppType(inst) ?? '').toLowerCase();\n return name.includes(term) || ws.includes(term) || type.includes(term);\n });\n }, [installations, search]);\n\n const sortedInstallations = useMemo(\n () => [...filteredInstallations].sort((a, b) => b.installedAt.localeCompare(a.installedAt)),\n [filteredInstallations]\n );\n\n const handleStatusChange = useCallback((status: StoreInstallationStatus | 'all') => {\n setSelectedStatus(status);\n setPage(1);\n }, []);\n\n const handleUninstall = async (installation: InstalledApp) => {\n setErrorMessage(null);\n const ok = await uninstall(installation, 'User requested uninstall from store UI');\n if (!ok) {\n setErrorMessage('Could not uninstall. Try again.');\n } else {\n apolloClient\n .mutate({\n mutation: UNINSTALL_APP_BY_APPLICATION,\n variables: {\n applicationId: installation.productId,\n workspaceId: installation.workspaceId,\n reason: 'User uninstalled from Installed Apps',\n },\n })\n .catch(() => {});\n }\n setPendingUninstall(null);\n };\n\n return (\n <div className=\"flex h-full flex-col\">\n <header className=\"border-b border-border-seam bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.installed.title', { defaultValue: 'Installed Apps' })}\n </h1>\n <p className=\"text-sm text-text-muted\">Manage your installed applications</p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"flex cursor-pointer items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 font-semibold text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Plus className=\"size-5\" />\n <span>{t('pages.installed.installNew', { defaultValue: 'Install New App' })}</span>\n </button>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-7xl\">\n <PagePurpose className=\"mb-4\">\n Every app you install into a workspace lives here. Check status, jump into details, or\n remove apps a workspace no longer needs.\n </PagePurpose>\n\n {/* Toolbar: status filters + search + refresh */}\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n {STATUS_FILTERS.map((f) => (\n <button\n type=\"button\"\n key={f.label}\n onClick={() => handleStatusChange(f.value)}\n className={`cursor-pointer rounded-full px-3 py-1.5 text-sm font-medium transition-colors ${\n selectedStatus === f.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-surface'\n }`}\n >\n {f.label}\n </button>\n ))}\n\n <div className=\"relative ml-auto\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-muted\" />\n <input\n type=\"search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name, workspace…\"\n className=\"h-9 rounded-lg border border-border-default bg-bg-surface pl-9 pr-3 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex cursor-pointer items-center gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n\n {errorMessage && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {errorMessage}\n </div>\n )}\n\n {loading ? (\n <output\n aria-label=\"Loading installed apps\"\n className=\"flex items-center justify-center py-16\"\n >\n <Loader2 className=\"size-8 animate-spin text-text-muted\" aria-hidden=\"true\" />\n </output>\n ) : error ? (\n <div\n role=\"alert\"\n className=\"flex flex-col items-center justify-center rounded-xl border border-status-error-border bg-status-error-bg-subtle py-16 text-center\"\n >\n <AlertCircle className=\"mb-3 size-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n Failed to load installations\n </h3>\n <p className=\"mt-2 text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"mt-4 cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n Try Again\n </button>\n </div>\n ) : sortedInstallations.length === 0 && totalCount === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={selectedStatus === 'all' ? 'No apps installed' : 'No apps match this filter'}\n description={\n selectedStatus === 'all'\n ? 'Browse the marketplace to install your first app.'\n : 'Try a different status filter, or browse the marketplace.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 font-semibold text-action-primary-text transition-colors hover:opacity-90\"\n >\n Browse Marketplace\n </button>\n }\n />\n ) : (\n <>\n <div className=\"mb-2 flex items-center justify-between text-sm text-text-muted\">\n <span aria-live=\"polite\">\n {search.trim()\n ? `${sortedInstallations.length} of ${totalCount} shown`\n : `${totalCount} installation${totalCount === 1 ? '' : 's'}`}\n </span>\n <span>\n Page {page} of {totalPages}\n </span>\n </div>\n\n {/* Table */}\n <div className=\"overflow-x-auto rounded-xl border border-border-seam\">\n <table className=\"w-full min-w-[720px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken text-left text-xs font-semibold uppercase tracking-wide text-text-muted\">\n <th className=\"px-4 py-3\">App</th>\n <th className=\"px-4 py-3\">Workspace</th>\n <th className=\"px-4 py-3\">Status</th>\n <th className=\"px-4 py-3\">Version</th>\n <th className=\"px-4 py-3\">Installed</th>\n <th className=\"px-4 py-3 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam bg-bg-surface\">\n {sortedInstallations.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"py-12 text-center text-text-muted\">\n No results match your search.\n </td>\n </tr>\n ) : (\n sortedInstallations.map((installation) => {\n const appName = getAppName(installation);\n const appType = getAppType(installation);\n const installationId = getInstallationId(installation);\n const uninstalling = uninstallingIds.has(\n `${installation.workspaceId}:${installation.productId}`\n );\n const isIntegration =\n appType === 'INTEGRATION' || appType === 'API_INTEGRATION';\n\n return (\n <tr\n key={installation.id}\n className=\"transition-colors hover:bg-bg-subtle\"\n >\n {/* App column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <AppAvatar installation={installation} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {appName}\n </p>\n {appType && (\n <span className=\"mt-0.5 inline-block rounded bg-bg-sunken px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {appType}\n </span>\n )}\n {isIntegration && installationId && (\n <p\n className=\"truncate text-[10px] text-text-muted\"\n title={installationId}\n >\n ID: {installationId.slice(0, 16)}…\n </p>\n )}\n </div>\n </div>\n </td>\n\n {/* Workspace column */}\n <td className=\"px-4 py-3\">\n <p className=\"truncate text-text-primary\">\n {installation.workspaceName ?? installation.workspaceId}\n </p>\n {installation.workspaceType && (\n <p className=\"text-xs text-text-muted\">\n {installation.workspaceType}\n </p>\n )}\n </td>\n\n {/* Status column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <span\n className={`inline-block rounded-full px-2 py-1 text-xs font-medium ${statusBadgeClass(installation.status)}`}\n >\n {installation.status}\n </span>\n {installation.productStatus === 'UNLISTED' && (\n <span className=\"inline-block rounded-full bg-status-warning-bg-subtle px-2 py-1 text-xs font-medium text-status-warning-text\">\n Delisted\n </span>\n )}\n </div>\n </td>\n\n {/* Version column */}\n <td className=\"px-4 py-3 font-variant-numeric tabular-nums text-text-muted\">\n {installation.version ? `v${installation.version}` : '—'}\n </td>\n\n {/* Installed date column */}\n <td className=\"px-4 py-3 text-text-muted\">\n {new Date(installation.installedAt).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })}\n </td>\n\n {/* Actions column */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-2\">\n {isIntegration ? (\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n title=\"View installation details\"\n >\n <ExternalLink className=\"size-3.5\" />\n Details\n </button>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/installed/${installation.id}`)\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Details\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n `${basePath}/installed/${installation.id}/settings`\n )\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Settings className=\"size-3.5\" />\n Configure\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n `${basePath}/marketplace/product/${installation.productId}`\n )\n }\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Product\n </button>\n {installation.status !== StoreInstallationStatus.Uninstalled && (\n <button\n type=\"button\"\n onClick={() => setPendingUninstall(installation)}\n disabled={uninstalling}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-status-error-border px-3 py-1.5 text-xs font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {uninstalling ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Uninstall\n </button>\n )}\n </div>\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n\n {/* Pagination controls */}\n {totalPages > 1 && (\n <div className=\"mt-4 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page === 1 || loading}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: Math.min(totalPages, 7) }, (_, i) => {\n const p =\n totalPages <= 7\n ? i + 1\n : i < 3\n ? i + 1\n : i >= 4\n ? totalPages - (6 - i)\n : page;\n return (\n <button\n key={p}\n type=\"button\"\n onClick={() => setPage(p)}\n className={`size-8 rounded-lg text-sm font-medium transition-colors ${\n p === page\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-muted hover:bg-bg-sunken'\n }`}\n >\n {p}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => setPage((p) => (hasMore ? p + 1 : Math.min(totalPages, p + 1)))}\n disabled={page >= totalPages || loading}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </div>\n </main>\n\n <AlertDialog\n open={pendingUninstall !== null}\n onOpenChange={(open) => !open && setPendingUninstall(null)}\n >\n <AlertDialogContent className=\"max-w-md\">\n <AlertDialogHeader>\n <AlertDialogTitle>Uninstall app</AlertDialogTitle>\n <AlertDialogDescription>\n {pendingUninstall && (\n <>\n Remove{' '}\n <span className=\"font-medium text-text-primary\">\n {getAppName(pendingUninstall)}\n </span>{' '}\n from{' '}\n <span className=\"font-medium text-text-primary\">\n {pendingUninstall.workspaceName ?? pendingUninstall.workspaceId}\n </span>\n ?{' '}\n {(getAppType(pendingUninstall) === 'INTEGRATION' ||\n getAppType(pendingUninstall) === 'API_INTEGRATION') && (\n <span className=\"mt-1 block text-xs text-text-muted\">\n This will also revoke the integration's access on the third-party side.\n </span>\n )}\n </>\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel onClick={() => setPendingUninstall(null)}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-status-error-bg text-status-error-text hover:bg-status-error-bg-emphasis\"\n onClick={() => pendingUninstall && handleUninstall(pendingUninstall)}\n >\n Uninstall\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { useCallback as te, useEffect as ne, useId as re, useMemo as c, useRef a
|
|
|
8
8
|
import { useI18n as ae } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
9
|
import { AlertCircle as u, Bot as d, Box as f, Brain as p, ChevronDown as oe, ChevronRight as m, Clock as se, Database as ce, Download as h, FileCode2 as le, Filter as ue, Gift as de, GitBranch as g, LayoutDashboard as _, Loader2 as fe, Puzzle as v, Search as pe, ShoppingCart as me, SlidersHorizontal as he, Sparkles as y, Star as b, TrendingUp as ge, X as x } from "lucide-react";
|
|
10
10
|
import { Fragment as _e, jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
11
|
-
import { useNavigate as ve } from "react-router
|
|
11
|
+
import { useNavigate as ve } from "react-router";
|
|
12
12
|
import { AuroraBackground as ye, GlassCard as be, GradientText as xe, IllustratedEmptyState as Se, PagePurpose as Ce } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
import { useEventBus as we } from "@burdenoff/fe-libs/shared/events";
|
|
14
14
|
import { useDebounce as Te } from "@burdenoff/fe-libs/shared/hooks";
|