@burdenoff/microfe-store 2026.703.1 → 2026.704.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/components/InstallAppModal.js +305 -140
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/generated/global-operations.js +7 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/dist/hooks/useInstallations.js.map +1 -1
- package/dist/hooks/useStoreGraphQL.js +42 -34
- package/dist/hooks/useStoreGraphQL.js.map +1 -1
- package/dist/hooks/useStoreGraphQLWithContext.js +10 -7
- package/dist/hooks/useStoreGraphQLWithContext.js.map +1 -1
- package/dist/pages/AdminDashboardPage.js +451 -378
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminReviewModerationPage.js +182 -150
- package/dist/pages/AdminReviewModerationPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +642 -305
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/AppDetailPage.js +200 -153
- package/dist/pages/AppDetailPage.js.map +1 -1
- package/dist/pages/AppSettingsPage.js.map +1 -1
- package/dist/pages/CartPage.js.map +1 -1
- package/dist/pages/InstalledAppsPage.js +335 -154
- package/dist/pages/InstalledAppsPage.js.map +1 -1
- package/dist/pages/MyLicensesPage.js.map +1 -1
- package/dist/pages/OrdersPage.js +5 -5
- package/dist/pages/ProductDetailPage.js +505 -451
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstalledAppsPage.js","names":[],"sources":["../../src/pages/InstalledAppsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Package, Plus, RefreshCw, Trash2, AlertCircle, Loader2 } 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';\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\n/**\n * InstalledAppsPage component\n *\n * Displays installed applications for the current actor\n */\nexport const InstalledAppsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath, workspaceId } = useStore();\n const { t } = useI18n();\n const [selectedStatus, setSelectedStatus] = useState<StoreInstallationStatus | 'all'>('all');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [pendingUninstall, setPendingUninstall] = useState<InstalledApp | null>(null);\n\n const filter: 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 loadMore,\n } = useInstallations({ filter });\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 sortedInstallations = useMemo(\n () => [...installations].sort((a, b) => b.installedAt.localeCompare(a.installedAt)),\n [installations]\n );\n\n const handleInstallNew = () => {\n navigate(`${basePath}/marketplace`);\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 }\n setPendingUninstall(null);\n };\n\n return (\n <div className=\"flex h-full flex-col\">\n {/* Header */}\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\n <button\n type=\"button\"\n onClick={handleInstallNew}\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 {/* Content */}\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-6xl\">\n <PagePurpose className=\"mb-4\">\n Every app you install into a workspace lives here. Use this page to check whether an app\n is active, healthy, or failed, jump into its settings, or remove apps a workspace no\n longer needs — keeping each workspace's installed surface lean and under control.\n </PagePurpose>\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n {STATUS_FILTERS.map((filter) => (\n <button\n type=\"button\"\n key={filter.label}\n onClick={() => setSelectedStatus(filter.value)}\n className={`cursor-pointer rounded-full px-3 py-1.5 text-sm font-medium transition-colors ${\n selectedStatus === filter.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-surface'\n }`}\n >\n {filter.label}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"ml-auto cursor-pointer inline-flex 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 ? (\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 into a workspace.'\n : 'Try a different status filter, or browse the marketplace to install something new.'\n }\n action={\n <button\n type=\"button\"\n onClick={handleInstallNew}\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 <div className=\"flex flex-col gap-y-4\">\n <p aria-live=\"polite\" className=\"text-sm text-text-muted\">\n {totalCount} installation{totalCount === 1 ? '' : 's'} found\n </p>\n <ul className=\"flex flex-col gap-y-4\">\n {sortedInstallations.map((installation) => {\n const uninstalling = uninstallingIds.has(\n `${installation.workspaceId}:${installation.productId}`\n );\n return (\n <li\n key={installation.id}\n className=\"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 <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-12 items-center justify-center rounded-lg bg-bg-sunken text-lg font-semibold text-text-muted\">\n <Package className=\"size-6\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {installation.workspaceName ?? installation.workspaceId}\n </h3>\n <p className=\"text-sm text-text-muted\">\n Product: {installation.productId}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-muted\">\n <span\n className={`rounded-full px-2 py-1 font-medium ${statusBadgeClass(installation.status)}`}\n >\n {installation.status}\n </span>\n {installation.version && <span>v{installation.version}</span>}\n {installation.workspaceType && (\n <span>{installation.workspaceType}</span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/installed/${installation.id}`)}\n className=\"cursor-pointer rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Details\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigate(`${basePath}/marketplace/product/${installation.productId}`)\n }\n className=\"cursor-pointer rounded-lg border border-border-default px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n View Product\n </button>\n {installation.status !== StoreInstallationStatus.Uninstalled && (\n <button\n type=\"button\"\n onClick={() => setPendingUninstall(installation)}\n disabled={uninstalling}\n className=\"cursor-pointer inline-flex items-center gap-1.5 rounded-lg border border-status-error-border px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {uninstalling ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n Uninstall\n </button>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ul>\n {hasMore && (\n <div className=\"flex justify-center pt-6\">\n <button\n type=\"button\"\n onClick={loadMore}\n disabled={loading}\n className=\"rounded-lg border border-border-default px-6 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </div>\n )}\n </div>\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 product{' '}\n <span className=\"font-medium text-text-primary\">\n {pendingUninstall.productId}\n </span>{' '}\n from workspace{' '}\n <span className=\"font-medium text-text-primary\">\n {pendingUninstall.workspaceName ?? pendingUninstall.workspaceId}\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-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 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 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 <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 </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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyLicensesPage.js","names":[],"sources":["../../src/pages/MyLicensesPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { ArrowLeft, ShieldCheck, AlertCircle, Loader2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { QuotaMeter } from '@burdenoff/fe-libs/shared/components';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useStore } from '../providers/StoreProvider';\nimport { useMyEntitlements } from '../hooks/useStoreGraphQL';\n\ninterface LicenseItem {\n id: string;\n productId: string;\n status: string;\n licenseKey?: string | null;\n expiresAt?: string | null;\n}\n\nconst statusClass = (status: string): string => {\n if (status === 'ACTIVE') return 'bg-status-success-bg-subtle text-status-success-text';\n if (status === 'SUSPENDED' || status === 'PENDING') {\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n }\n if (status === 'EXPIRED' || status === 'REVOKED') {\n return 'bg-status-error-bg-subtle text-status-error-text';\n }\n return 'bg-bg-sunken text-text-muted';\n};\n\nexport const MyLicensesPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { t } = useI18n();\n const { entitlements, hasMore, loading, error, refetch, loadMore } = useMyEntitlements();\n\n const licenses = useMemo(\n () => (entitlements as LicenseItem[]).slice().sort((a, b) => a.id.localeCompare(b.id)),\n [entitlements]\n );\n\n return (\n <div className=\"flex h-full flex-col\">\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 flex max-w-5xl 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>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.licenses.title', { defaultValue: 'My Licenses' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.licenses.subtitle', {\n defaultValue: 'Review and manage your product entitlements',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-5xl\">\n <PagePurpose className=\"mb-6\">\n Every paid app you own grants a license (entitlement) that proves your right to use it.\n Check here to confirm a license is active, find a license key to enter into a product,\n and see when paid access expires — so you're never caught out by a lapsed or suspended\n entitlement.\n </PagePurpose>\n {loading ? (\n <output\n aria-label=\"Loading licenses\"\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=\"rounded-xl border border-status-error-border bg-status-error-bg-subtle p-8 text-center\"\n >\n <AlertCircle className=\"mx-auto mb-3 size-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n {t('pages.licenses.failedToLoad', { defaultValue: 'Failed to load licenses' })}\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 {t('common.tryAgain', { defaultValue: 'Try Again' })}\n </button>\n </div>\n ) : licenses.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('pages.licenses.emptyTitle', { defaultValue: 'No licenses yet' })}\n description={t('pages.licenses.emptyDescription', {\n defaultValue: 'Purchase apps from the marketplace to see your licenses here.',\n })}\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {t('pages.licenses.browseMarketplace', { defaultValue: 'Browse Marketplace' })}\n </button>\n }\n />\n ) : (\n <>\n {/*\n Inline quota meter showing active-license consumption vs the\n workspace plan limit. The limit is sourced from the billing\n summary when available; passing `null` renders an\n \"Unlimited\" indicator instead of misleading 0/0 progress.\n */}\n <EmphasisPanel className=\"mb-6\">\n <QuotaMeter\n label={t('pages.licenses.activeLicenses', {\n defaultValue: 'Active licenses',\n })}\n used={licenses.filter((l) => l.status === 'ACTIVE').length}\n limit={null}\n unit={t('pages.licenses.licensesUnit', {\n defaultValue: 'licenses',\n })}\n />\n </EmphasisPanel>\n <ul className=\"flex flex-col gap-y-4\">\n {licenses.map((license) => (\n <li\n key={license.id}\n className=\"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 <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-status-success-text\" />\n <h3 className=\"font-semibold text-text-primary\">\n Product {license.productId}\n </h3>\n </div>\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.entitlementId', {\n defaultValue: `Entitlement ID: ${license.id}`,\n id: license.id,\n })}\n </p>\n {license.licenseKey && (\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.licenseKey', {\n defaultValue: `License key: ${license.licenseKey}`,\n key: license.licenseKey,\n })}\n </p>\n )}\n {license.expiresAt && (\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.expiresOn', {\n defaultValue: `Expires on ${new Date(license.expiresAt).toLocaleDateString()}`,\n date: new Date(license.expiresAt).toLocaleDateString(),\n })}\n </p>\n )}\n </div>\n <span\n className={`rounded-full px-3 py-1 text-xs font-semibold ${statusClass(license.status)} ${\n license.status === 'ACTIVE'\n ? 'motion-safe:animate-vc-glow-pulse'\n : ''\n }`}\n >\n {license.status}\n </span>\n </div>\n </li>\n ))}\n {hasMore && (\n <li className=\"flex justify-center pt-4 list-none\">\n <button\n type=\"button\"\n onClick={loadMore}\n disabled={loading}\n className=\"rounded-lg border border-border-default px-6 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </li>\n )}\n </ul>\n </>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MyLicensesPage.js","names":[],"sources":["../../src/pages/MyLicensesPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { ArrowLeft, ShieldCheck, AlertCircle, Loader2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { QuotaMeter } from '@burdenoff/fe-libs/shared/components';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useStore } from '../providers/StoreProvider';\nimport { useMyEntitlements } from '../hooks/useStoreGraphQL';\n\ninterface LicenseItem {\n id: string;\n productId: string;\n status: string;\n licenseKey?: string | null;\n expiresAt?: string | null;\n}\n\nconst statusClass = (status: string): string => {\n if (status === 'ACTIVE') return 'bg-status-success-bg-subtle text-status-success-text';\n if (status === 'SUSPENDED' || status === 'PENDING') {\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n }\n if (status === 'EXPIRED' || status === 'REVOKED') {\n return 'bg-status-error-bg-subtle text-status-error-text';\n }\n return 'bg-bg-sunken text-text-muted';\n};\n\nexport const MyLicensesPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { t } = useI18n();\n const { entitlements, hasMore, loading, error, refetch, loadMore } = useMyEntitlements();\n\n const licenses = useMemo(\n () => (entitlements as LicenseItem[]).slice().sort((a, b) => a.id.localeCompare(b.id)),\n [entitlements]\n );\n\n return (\n <div className=\"flex h-full flex-col\">\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 flex max-w-5xl 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>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {t('pages.licenses.title', { defaultValue: 'My Licenses' })}\n </h1>\n <p className=\"text-sm text-text-muted\">\n {t('pages.licenses.subtitle', {\n defaultValue: 'Review and manage your product entitlements',\n })}\n </p>\n </div>\n </div>\n </header>\n\n <main className=\"flex-1 overflow-y-auto p-6\">\n <div className=\"mx-auto max-w-5xl\">\n <PagePurpose className=\"mb-6\">\n Every paid app you own grants a license (entitlement) that proves your right to use it.\n Check here to confirm a license is active, find a license key to enter into a product,\n and see when paid access expires — so you're never caught out by a lapsed or suspended\n entitlement.\n </PagePurpose>\n {loading ? (\n <output\n aria-label=\"Loading licenses\"\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=\"rounded-xl border border-status-error-border bg-status-error-bg-subtle p-8 text-center\"\n >\n <AlertCircle className=\"mx-auto mb-3 size-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-status-error-text\">\n {t('pages.licenses.failedToLoad', { defaultValue: 'Failed to load licenses' })}\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 {t('common.tryAgain', { defaultValue: 'Try Again' })}\n </button>\n </div>\n ) : licenses.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('pages.licenses.emptyTitle', { defaultValue: 'No licenses yet' })}\n description={t('pages.licenses.emptyDescription', {\n defaultValue: 'Purchase apps from the marketplace to see your licenses here.',\n })}\n action={\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n {t('pages.licenses.browseMarketplace', { defaultValue: 'Browse Marketplace' })}\n </button>\n }\n />\n ) : (\n <>\n {/*\n Inline quota meter showing active-license consumption vs the\n workspace plan limit. The limit is sourced from the billing\n summary when available; passing `null` renders an\n \"Unlimited\" indicator instead of misleading 0/0 progress.\n */}\n <EmphasisPanel className=\"mb-6\">\n <QuotaMeter\n label={t('pages.licenses.activeLicenses', {\n defaultValue: 'Active licenses',\n })}\n used={licenses.filter((l) => l.status === 'ACTIVE').length}\n limit={null}\n unit={t('pages.licenses.licensesUnit', {\n defaultValue: 'licenses',\n })}\n />\n </EmphasisPanel>\n <ul className=\"flex flex-col gap-y-4\">\n {licenses.map((license) => (\n <li\n key={license.id}\n className=\"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 <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"size-4 text-status-success-text\" />\n <h3 className=\"font-semibold text-text-primary\">\n Product {license.productId}\n </h3>\n </div>\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.entitlementId', {\n defaultValue: `Entitlement ID: ${license.id}`,\n id: license.id,\n })}\n </p>\n {license.licenseKey && (\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.licenseKey', {\n defaultValue: `License key: ${license.licenseKey}`,\n key: license.licenseKey,\n })}\n </p>\n )}\n {license.expiresAt && (\n <p className=\"mt-1 text-sm text-text-muted\">\n {t('pages.licenses.expiresOn', {\n defaultValue: `Expires on ${new Date(license.expiresAt).toLocaleDateString()}`,\n date: new Date(license.expiresAt).toLocaleDateString(),\n })}\n </p>\n )}\n </div>\n <span\n className={`rounded-full px-3 py-1 text-xs font-semibold ${statusClass(license.status)} ${\n license.status === 'ACTIVE' ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n >\n {license.status}\n </span>\n </div>\n </li>\n ))}\n {hasMore && (\n <li className=\"flex justify-center pt-4 list-none\">\n <button\n type=\"button\"\n onClick={loadMore}\n disabled={loading}\n className=\"rounded-lg border border-border-default px-6 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-subtle disabled:opacity-50\"\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </li>\n )}\n </ul>\n </>\n )}\n </div>\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
package/dist/pages/OrdersPage.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useStore as e } from "../providers/StoreProvider.js";
|
|
2
2
|
import { useOrderStatusUpdatedSubscription as t } from "../generated/global-operations.js";
|
|
3
3
|
import { formatPrice as n } from "../utils/index.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { InstallAppModal as r } from "../components/InstallAppModal.js";
|
|
5
|
+
import { useInstallations as i } from "../hooks/useInstallations.js";
|
|
6
|
+
import { useOrders as a } from "../hooks/useOrders.js";
|
|
7
7
|
import { useMemo as o, useState as s } from "react";
|
|
8
8
|
import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
9
|
import { ArrowLeft as l, CheckCircle as u, Clock as d, Download as f, Loader2 as p, Package as m, RefreshCw as h, ShoppingBag as g, XCircle as _ } from "lucide-react";
|
|
@@ -184,7 +184,7 @@ var T = (e) => {
|
|
|
184
184
|
]
|
|
185
185
|
});
|
|
186
186
|
}, O = () => {
|
|
187
|
-
let n = x(), { basePath: u } = e(), { t: d } = c(), { orders: f, loading: h, error: v, refetchOrders: S, hasMore: T, loadMore: E } =
|
|
187
|
+
let n = x(), { basePath: u } = e(), { t: d } = c(), { orders: f, loading: h, error: v, refetchOrders: S, hasMore: T, loadMore: E } = a(), { installations: O, refetch: k } = i();
|
|
188
188
|
t({
|
|
189
189
|
onData: () => {
|
|
190
190
|
S();
|
|
@@ -335,7 +335,7 @@ var T = (e) => {
|
|
|
335
335
|
})]
|
|
336
336
|
})]
|
|
337
337
|
}),
|
|
338
|
-
P && /* @__PURE__ */ y(
|
|
338
|
+
P && /* @__PURE__ */ y(r, {
|
|
339
339
|
isOpen: A,
|
|
340
340
|
onClose: V,
|
|
341
341
|
app: P,
|