@burdenoff/microfe-store 2026.616.1 → 2026.623.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.
|
@@ -165,7 +165,9 @@ var E = ({ isOpen: t, onClose: n, app: i, orderId: h, purchaseState: x = "free",
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
|
-
authToken: O || void 0
|
|
168
|
+
authToken: O || void 0,
|
|
169
|
+
workspaceId: e.id,
|
|
170
|
+
workspaceToken: !0
|
|
169
171
|
});
|
|
170
172
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Installation failed");
|
|
171
173
|
let r = t.data?.installWorkspaceApps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstallAppModal.js","names":[],"sources":["../../src/components/InstallAppModal.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useEffect, useCallback, useReducer } from 'react';\nimport { X, Loader2, Building2, CheckCircle, AlertCircle, Info } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useStore } from '../providers/StoreProvider';\n\n// Local type definitions for workspace operations\ninterface Workspace {\n id: string;\n name: string;\n slug: string;\n type?: string;\n status?: string;\n organizationId?: string;\n}\n\ninterface WorkspaceList {\n items: Workspace[];\n total: number;\n}\n\ninterface InstallWorkspaceAppsResult {\n successCount: number;\n failedCount: number;\n results: Array<{\n success: boolean;\n workspaceId: string;\n app?: unknown;\n error?: string;\n }>;\n}\n\n// String queries for graphqlFetch\nconst MY_WORKSPACES_STRING = `query MyWorkspaces { myWorkspaces { items { id name slug type status organizationId } total } }`;\nconst INSTALL_WORKSPACE_APPS_STRING = `mutation InstallWorkspaceApps($workspaceIds: [ID!]!, $input: InstallWorkspaceAppInput!) { installWorkspaceApps(workspaceIds: $workspaceIds, input: $input) { successCount failedCount results { success workspaceId error } } }`;\nconst RECORD_APP_INSTALLATION_STRING = `mutation RecordAppInstallation($input: RecordAppInstallationInput!) { recordAppInstallation(input: $input) { id status workspaceId version } }`;\nconst INSTALL_PURCHASED_APP_STRING = `mutation InstallPurchasedApp($orderId: ID, $productId: String!, $workspaceId: String!) { installPurchasedApp(orderId: $orderId, productId: $productId, workspaceId: $workspaceId) { success alreadyInstalled installation { id productId workspaceId status version } error message } }`;\nconst CREATE_PRODUCT_ENTITLEMENT_STRING = `mutation CreateProductEntitlement($input: CreateProductEntitlementInput!) { createProductEntitlement(input: $input) { id status productId workspaceId } }`;\nconst STORE_DISTRIBUTED_TYPES = new Set([\n 'VIBEMODULE',\n 'NODE',\n 'CONSOLE',\n 'DASHBOARD',\n 'PARSER',\n 'DATASINK',\n]);\n\nconst INSTALL_ERROR_MESSAGES: Record<string, string> = {\n ALREADY_INSTALLED: 'This app is already installed in the selected workspace.',\n QUOTA_EXCEEDED: 'Installation limit reached. Please upgrade your plan or contact support.',\n PERMISSION_DENIED: 'You do not have permission to install apps in this workspace.',\n NOT_FOUND: 'The app or workspace could not be found.',\n WORKSPACE_INACTIVE: 'The selected workspace is inactive.',\n ENTITLEMENT_EXPIRED: 'Your license for this app has expired.',\n UNSUPPORTED_TYPE: 'This product type cannot be installed in the selected workspace.',\n};\n\nfunction mapInstallError(raw: string): string {\n for (const [code, message] of Object.entries(INSTALL_ERROR_MESSAGES)) {\n if (raw.toUpperCase().includes(code)) return message;\n }\n return 'Installation failed. Please try again or contact support.';\n}\n\ninterface InstallState {\n selectedWorkspace: string | null;\n installing: boolean;\n installError: string | null;\n installInfo: string | null;\n}\n\ntype InstallAction =\n | { type: 'reset' }\n | { type: 'selectWorkspace'; workspaceId: string }\n | { type: 'startInstall' }\n | { type: 'finishInstall' }\n | { type: 'setError'; error: string }\n | { type: 'setInfo'; info: string };\n\nconst initialInstallState: InstallState = {\n selectedWorkspace: null,\n installing: false,\n installError: null,\n installInfo: null,\n};\n\nfunction installReducer(state: InstallState, action: InstallAction): InstallState {\n switch (action.type) {\n case 'reset':\n return initialInstallState;\n case 'selectWorkspace':\n return { ...state, selectedWorkspace: action.workspaceId };\n case 'startInstall':\n return { ...state, installing: true, installError: null, installInfo: null };\n case 'finishInstall':\n return { ...state, installing: false };\n case 'setError':\n return { ...state, installError: action.error };\n case 'setInfo':\n return { ...state, installInfo: action.info };\n default:\n return state;\n }\n}\n\nexport interface AppInfo {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type?: string;\n version?: string;\n}\n\ninterface InstallAppModalProps {\n isOpen: boolean;\n onClose: () => void;\n app: AppInfo;\n orderId?: string | null; // For purchased apps from orders page\n purchaseState?: 'free' | 'purchased';\n onInstallSuccess?: (workspaceId: string, workspaceName: string) => void;\n onInstallError?: (error: Error) => void;\n}\n\ninterface UseWorkspacesResult {\n workspaces: Workspace[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n// NOTE: Inline Apollo clients removed. All GraphQL calls now use graphqlFetch\n// from fe-libs which automatically resolves gateway URLs and injects auth + locale headers.\n\n/**\n * Hook to fetch user's workspaces using graphqlFetch from fe-libs\n */\nfunction useWorkspaces(skip: boolean): UseWorkspacesResult {\n const { authToken } = useStore();\n const [workspaces, setWorkspaces] = useState<Workspace[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchWorkspaces = useCallback(async () => {\n if (skip) return;\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await graphqlFetch<{ myWorkspaces: WorkspaceList }>({\n gateway: 'global',\n query: MY_WORKSPACES_STRING,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'GraphQL error');\n }\n\n setWorkspaces(result.data?.myWorkspaces?.items || []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch workspaces'));\n } finally {\n setLoading(false);\n }\n }, [authToken, skip]);\n\n useEffect(() => {\n fetchWorkspaces();\n }, [fetchWorkspaces]);\n\n return {\n workspaces,\n loading,\n error,\n refetch: fetchWorkspaces,\n };\n}\n\n/**\n * InstallAppModal component\n *\n * Modal for selecting a workspace to install an app into\n */\nexport const InstallAppModal: FC<InstallAppModalProps> = ({\n isOpen,\n onClose,\n app,\n orderId,\n purchaseState = 'free',\n onInstallSuccess,\n onInstallError,\n}) => {\n const { authToken, user } = useStore();\n const [installState, dispatchInstall] = useReducer(installReducer, initialInstallState);\n const { selectedWorkspace, installing, installError, installInfo } = installState;\n\n const { workspaces, loading, error } = useWorkspaces(!isOpen);\n\n // Filter workspaces by status (only show active)\n const filteredWorkspaces = workspaces.filter((ws) => ws.status === 'ACTIVE');\n\n /**\n * Install purchased app via Store Service (NEW FLOW)\n * Used when installing from orders page or any already-purchased product\n */\n const handleInstallFromStore = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n const purchasedBy = user?.id;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n if (!orderId && purchaseState !== 'purchased') {\n if (!purchasedBy) {\n throw new Error('Unable to identify the current user for license creation');\n }\n\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_PRODUCT_ENTITLEMENT_STRING,\n variables: {\n input: {\n productId: app.id,\n purchasedBy,\n workspaceId: workspace.id,\n metadata: {\n source: 'store-ui',\n productType: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n }\n\n // Call Store Service to install the purchased app\n const result = await graphqlFetch<{\n installPurchasedApp: {\n success: boolean;\n alreadyInstalled?: boolean;\n installation?: {\n id: string;\n productId: string;\n workspaceId: string;\n status: string;\n version: string;\n };\n error?: string;\n message?: string;\n };\n }>({\n gateway: 'global', // Store Service\n query: INSTALL_PURCHASED_APP_STRING,\n variables: {\n orderId: orderId ?? null,\n productId: app.id,\n workspaceId: workspace.id,\n },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installPurchasedApp;\n\n if (installResult?.success) {\n if (installResult.alreadyInstalled) {\n dispatchInstall({\n type: 'setInfo',\n info: installResult.message || 'Already installed on this workspace',\n });\n } else {\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n }\n } else {\n const rawMsg = installResult?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Install app via Workspace Service (LEGACY FLOW)\n * Used for free apps or when installing without order\n */\n const handleInstallViaWorkspace = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n // Step 1: Install to workspace service via graphqlFetch\n const installVars = {\n workspaceIds: [workspace.id],\n input: {\n appName: app.slug || app.name.toLowerCase().replace(/\\s+/g, '-'),\n displayName: app.name,\n version: app.version || '1.0.0',\n type: app.type || 'app',\n manifest: {\n productId: app.id,\n icon: app.icon,\n },\n },\n };\n\n const result = await graphqlFetch<{\n installWorkspaceApps: InstallWorkspaceAppsResult;\n }>({\n gateway: 'workspace',\n query: INSTALL_WORKSPACE_APPS_STRING,\n variables: installVars,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installWorkspaceApps;\n if (installResult?.successCount && installResult.successCount > 0) {\n // Step 2: Record installation in store service (for tracking)\n try {\n await graphqlFetch({\n gateway: 'global',\n query: RECORD_APP_INSTALLATION_STRING,\n variables: {\n input: {\n productId: app.id,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n workspaceType: workspace.type,\n organizationId: workspace.organizationId,\n version: app.version || '1.0.0',\n manifest: {\n productId: app.id,\n icon: app.icon,\n slug: app.slug,\n type: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n } catch {\n // Non-fatal: workspace installation succeeded; store record will sync later.\n }\n\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n } else {\n const rawMsg = installResult?.results?.[0]?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Main install handler - routes to appropriate install function\n */\n const handleInstall = async () => {\n const appType = app.type?.toUpperCase() ?? '';\n\n if (purchaseState === 'purchased' || orderId || STORE_DISTRIBUTED_TYPES.has(appType)) {\n // Purchased products install through the store service.\n await handleInstallFromStore();\n } else {\n // Free products install through the workspace service.\n await handleInstallViaWorkspace();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close modal\"\n className=\"absolute inset-0 bg-bg-overlay/50 backdrop-blur-sm\"\n onClick={onClose}\n />\n\n {/* Modal */}\n <dialog\n open\n aria-modal=\"true\"\n aria-labelledby=\"install-app-modal-title\"\n className=\"relative z-10 mx-4 w-full max-w-lg rounded-2xl border border-border-default bg-bg-surface shadow-2xl\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-border-default px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n {app.icon ? (\n <img src={app.icon} alt={app.name} className=\"size-10 rounded-lg object-cover\" />\n ) : (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted\">\n {app.name.charAt(0)}\n </div>\n )}\n <div>\n <h2 id=\"install-app-modal-title\" className=\"text-lg font-semibold text-text-primary\">\n Install App\n </h2>\n <p className=\"text-sm text-text-muted\">{app.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close install dialog\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-6 py-4\">\n <p className=\"mb-4 text-text-secondary\">\n Select the workspace where you want to install this app:\n </p>\n\n {/* Workspace List */}\n <div className=\"max-h-72 overflow-y-auto rounded-lg border border-border-default\">\n {loading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 animate-spin text-text-muted\" />\n </div>\n ) : error ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle className=\"mb-2 size-8 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">Failed to load workspaces</p>\n <p className=\"text-xs text-text-muted mt-1\">{error.message}</p>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Building2 className=\"mb-2 size-8 text-text-muted\" />\n <p className=\"text-sm text-text-muted\">No workspaces available</p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {filteredWorkspaces.map((workspace) => (\n <button\n type=\"button\"\n key={workspace.id}\n onClick={() =>\n dispatchInstall({ type: 'selectWorkspace', workspaceId: workspace.id })\n }\n className={`w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${\n selectedWorkspace === workspace.id\n ? 'bg-action-primary-bg/10'\n : 'transition-colors hover:bg-bg-sunken'\n }`}\n >\n {/* Workspace Icon */}\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-text-link\">\n <Building2 className=\"size-5\" />\n </div>\n\n {/* Workspace Info */}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{workspace.name}</p>\n {workspace.slug ? (\n <p className=\"text-xs text-text-muted truncate\">/{workspace.slug}</p>\n ) : null}\n </div>\n\n {/* Selection Indicator */}\n {selectedWorkspace === workspace.id && (\n <CheckCircle className=\"size-5 flex-shrink-0 text-action-primary-bg\" />\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-default px-6 py-4\">\n {/* Already-installed info */}\n {installInfo && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-info-bg-subtle px-3 py-2 text-sm text-status-info-text\">\n <Info className=\"size-4 flex-shrink-0\" />\n <span>{installInfo}</span>\n </div>\n )}\n {/* Error message */}\n {installError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-error-bg-subtle/10 px-3 py-2 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <span>{installError}</span>\n </div>\n )}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={installing}\n className=\"cursor-pointer rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleInstall}\n disabled={!selectedWorkspace || installing}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2\"\n >\n {installing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Installing…\n </>\n ) : (\n 'Install App'\n )}\n </button>\n </div>\n </div>\n </dialog>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InstallAppModal.js","names":[],"sources":["../../src/components/InstallAppModal.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useEffect, useCallback, useReducer } from 'react';\nimport { X, Loader2, Building2, CheckCircle, AlertCircle, Info } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useStore } from '../providers/StoreProvider';\n\n// Local type definitions for workspace operations\ninterface Workspace {\n id: string;\n name: string;\n slug: string;\n type?: string;\n status?: string;\n organizationId?: string;\n}\n\ninterface WorkspaceList {\n items: Workspace[];\n total: number;\n}\n\ninterface InstallWorkspaceAppsResult {\n successCount: number;\n failedCount: number;\n results: Array<{\n success: boolean;\n workspaceId: string;\n app?: unknown;\n error?: string;\n }>;\n}\n\n// String queries for graphqlFetch\nconst MY_WORKSPACES_STRING = `query MyWorkspaces { myWorkspaces { items { id name slug type status organizationId } total } }`;\nconst INSTALL_WORKSPACE_APPS_STRING = `mutation InstallWorkspaceApps($workspaceIds: [ID!]!, $input: InstallWorkspaceAppInput!) { installWorkspaceApps(workspaceIds: $workspaceIds, input: $input) { successCount failedCount results { success workspaceId error } } }`;\nconst RECORD_APP_INSTALLATION_STRING = `mutation RecordAppInstallation($input: RecordAppInstallationInput!) { recordAppInstallation(input: $input) { id status workspaceId version } }`;\nconst INSTALL_PURCHASED_APP_STRING = `mutation InstallPurchasedApp($orderId: ID, $productId: String!, $workspaceId: String!) { installPurchasedApp(orderId: $orderId, productId: $productId, workspaceId: $workspaceId) { success alreadyInstalled installation { id productId workspaceId status version } error message } }`;\nconst CREATE_PRODUCT_ENTITLEMENT_STRING = `mutation CreateProductEntitlement($input: CreateProductEntitlementInput!) { createProductEntitlement(input: $input) { id status productId workspaceId } }`;\nconst STORE_DISTRIBUTED_TYPES = new Set([\n 'VIBEMODULE',\n 'NODE',\n 'CONSOLE',\n 'DASHBOARD',\n 'PARSER',\n 'DATASINK',\n]);\n\nconst INSTALL_ERROR_MESSAGES: Record<string, string> = {\n ALREADY_INSTALLED: 'This app is already installed in the selected workspace.',\n QUOTA_EXCEEDED: 'Installation limit reached. Please upgrade your plan or contact support.',\n PERMISSION_DENIED: 'You do not have permission to install apps in this workspace.',\n NOT_FOUND: 'The app or workspace could not be found.',\n WORKSPACE_INACTIVE: 'The selected workspace is inactive.',\n ENTITLEMENT_EXPIRED: 'Your license for this app has expired.',\n UNSUPPORTED_TYPE: 'This product type cannot be installed in the selected workspace.',\n};\n\nfunction mapInstallError(raw: string): string {\n for (const [code, message] of Object.entries(INSTALL_ERROR_MESSAGES)) {\n if (raw.toUpperCase().includes(code)) return message;\n }\n return 'Installation failed. Please try again or contact support.';\n}\n\ninterface InstallState {\n selectedWorkspace: string | null;\n installing: boolean;\n installError: string | null;\n installInfo: string | null;\n}\n\ntype InstallAction =\n | { type: 'reset' }\n | { type: 'selectWorkspace'; workspaceId: string }\n | { type: 'startInstall' }\n | { type: 'finishInstall' }\n | { type: 'setError'; error: string }\n | { type: 'setInfo'; info: string };\n\nconst initialInstallState: InstallState = {\n selectedWorkspace: null,\n installing: false,\n installError: null,\n installInfo: null,\n};\n\nfunction installReducer(state: InstallState, action: InstallAction): InstallState {\n switch (action.type) {\n case 'reset':\n return initialInstallState;\n case 'selectWorkspace':\n return { ...state, selectedWorkspace: action.workspaceId };\n case 'startInstall':\n return { ...state, installing: true, installError: null, installInfo: null };\n case 'finishInstall':\n return { ...state, installing: false };\n case 'setError':\n return { ...state, installError: action.error };\n case 'setInfo':\n return { ...state, installInfo: action.info };\n default:\n return state;\n }\n}\n\nexport interface AppInfo {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type?: string;\n version?: string;\n}\n\ninterface InstallAppModalProps {\n isOpen: boolean;\n onClose: () => void;\n app: AppInfo;\n orderId?: string | null; // For purchased apps from orders page\n purchaseState?: 'free' | 'purchased';\n onInstallSuccess?: (workspaceId: string, workspaceName: string) => void;\n onInstallError?: (error: Error) => void;\n}\n\ninterface UseWorkspacesResult {\n workspaces: Workspace[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n// NOTE: Inline Apollo clients removed. All GraphQL calls now use graphqlFetch\n// from fe-libs which automatically resolves gateway URLs and injects auth + locale headers.\n\n/**\n * Hook to fetch user's workspaces using graphqlFetch from fe-libs\n */\nfunction useWorkspaces(skip: boolean): UseWorkspacesResult {\n const { authToken } = useStore();\n const [workspaces, setWorkspaces] = useState<Workspace[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchWorkspaces = useCallback(async () => {\n if (skip) return;\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await graphqlFetch<{ myWorkspaces: WorkspaceList }>({\n gateway: 'global',\n query: MY_WORKSPACES_STRING,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'GraphQL error');\n }\n\n setWorkspaces(result.data?.myWorkspaces?.items || []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch workspaces'));\n } finally {\n setLoading(false);\n }\n }, [authToken, skip]);\n\n useEffect(() => {\n fetchWorkspaces();\n }, [fetchWorkspaces]);\n\n return {\n workspaces,\n loading,\n error,\n refetch: fetchWorkspaces,\n };\n}\n\n/**\n * InstallAppModal component\n *\n * Modal for selecting a workspace to install an app into\n */\nexport const InstallAppModal: FC<InstallAppModalProps> = ({\n isOpen,\n onClose,\n app,\n orderId,\n purchaseState = 'free',\n onInstallSuccess,\n onInstallError,\n}) => {\n const { authToken, user } = useStore();\n const [installState, dispatchInstall] = useReducer(installReducer, initialInstallState);\n const { selectedWorkspace, installing, installError, installInfo } = installState;\n\n const { workspaces, loading, error } = useWorkspaces(!isOpen);\n\n // Filter workspaces by status (only show active)\n const filteredWorkspaces = workspaces.filter((ws) => ws.status === 'ACTIVE');\n\n /**\n * Install purchased app via Store Service (NEW FLOW)\n * Used when installing from orders page or any already-purchased product\n */\n const handleInstallFromStore = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n const purchasedBy = user?.id;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n if (!orderId && purchaseState !== 'purchased') {\n if (!purchasedBy) {\n throw new Error('Unable to identify the current user for license creation');\n }\n\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_PRODUCT_ENTITLEMENT_STRING,\n variables: {\n input: {\n productId: app.id,\n purchasedBy,\n workspaceId: workspace.id,\n metadata: {\n source: 'store-ui',\n productType: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n }\n\n // Call Store Service to install the purchased app\n const result = await graphqlFetch<{\n installPurchasedApp: {\n success: boolean;\n alreadyInstalled?: boolean;\n installation?: {\n id: string;\n productId: string;\n workspaceId: string;\n status: string;\n version: string;\n };\n error?: string;\n message?: string;\n };\n }>({\n gateway: 'global', // Store Service\n query: INSTALL_PURCHASED_APP_STRING,\n variables: {\n orderId: orderId ?? null,\n productId: app.id,\n workspaceId: workspace.id,\n },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installPurchasedApp;\n\n if (installResult?.success) {\n if (installResult.alreadyInstalled) {\n dispatchInstall({\n type: 'setInfo',\n info: installResult.message || 'Already installed on this workspace',\n });\n } else {\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n }\n } else {\n const rawMsg = installResult?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Install app via Workspace Service (LEGACY FLOW)\n * Used for free apps or when installing without order\n */\n const handleInstallViaWorkspace = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n // Step 1: Install to workspace service via graphqlFetch\n const installVars = {\n workspaceIds: [workspace.id],\n input: {\n appName: app.slug || app.name.toLowerCase().replace(/\\s+/g, '-'),\n displayName: app.name,\n version: app.version || '1.0.0',\n type: app.type || 'app',\n manifest: {\n productId: app.id,\n icon: app.icon,\n },\n },\n };\n\n const result = await graphqlFetch<{\n installWorkspaceApps: InstallWorkspaceAppsResult;\n }>({\n gateway: 'workspace',\n query: INSTALL_WORKSPACE_APPS_STRING,\n variables: installVars,\n authToken: authToken || undefined,\n workspaceId: workspace.id,\n workspaceToken: true,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installWorkspaceApps;\n if (installResult?.successCount && installResult.successCount > 0) {\n // Step 2: Record installation in store service (for tracking)\n try {\n await graphqlFetch({\n gateway: 'global',\n query: RECORD_APP_INSTALLATION_STRING,\n variables: {\n input: {\n productId: app.id,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n workspaceType: workspace.type,\n organizationId: workspace.organizationId,\n version: app.version || '1.0.0',\n manifest: {\n productId: app.id,\n icon: app.icon,\n slug: app.slug,\n type: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n } catch {\n // Non-fatal: workspace installation succeeded; store record will sync later.\n }\n\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n } else {\n const rawMsg = installResult?.results?.[0]?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Main install handler - routes to appropriate install function\n */\n const handleInstall = async () => {\n const appType = app.type?.toUpperCase() ?? '';\n\n if (purchaseState === 'purchased' || orderId || STORE_DISTRIBUTED_TYPES.has(appType)) {\n // Purchased products install through the store service.\n await handleInstallFromStore();\n } else {\n // Free products install through the workspace service.\n await handleInstallViaWorkspace();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close modal\"\n className=\"absolute inset-0 bg-bg-overlay/50 backdrop-blur-sm\"\n onClick={onClose}\n />\n\n {/* Modal */}\n <dialog\n open\n aria-modal=\"true\"\n aria-labelledby=\"install-app-modal-title\"\n className=\"relative z-10 mx-4 w-full max-w-lg rounded-2xl border border-border-default bg-bg-surface shadow-2xl\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-border-default px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n {app.icon ? (\n <img src={app.icon} alt={app.name} className=\"size-10 rounded-lg object-cover\" />\n ) : (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted\">\n {app.name.charAt(0)}\n </div>\n )}\n <div>\n <h2 id=\"install-app-modal-title\" className=\"text-lg font-semibold text-text-primary\">\n Install App\n </h2>\n <p className=\"text-sm text-text-muted\">{app.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close install dialog\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-6 py-4\">\n <p className=\"mb-4 text-text-secondary\">\n Select the workspace where you want to install this app:\n </p>\n\n {/* Workspace List */}\n <div className=\"max-h-72 overflow-y-auto rounded-lg border border-border-default\">\n {loading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 animate-spin text-text-muted\" />\n </div>\n ) : error ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle className=\"mb-2 size-8 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">Failed to load workspaces</p>\n <p className=\"text-xs text-text-muted mt-1\">{error.message}</p>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Building2 className=\"mb-2 size-8 text-text-muted\" />\n <p className=\"text-sm text-text-muted\">No workspaces available</p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {filteredWorkspaces.map((workspace) => (\n <button\n type=\"button\"\n key={workspace.id}\n onClick={() =>\n dispatchInstall({ type: 'selectWorkspace', workspaceId: workspace.id })\n }\n className={`w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${\n selectedWorkspace === workspace.id\n ? 'bg-action-primary-bg/10'\n : 'transition-colors hover:bg-bg-sunken'\n }`}\n >\n {/* Workspace Icon */}\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-text-link\">\n <Building2 className=\"size-5\" />\n </div>\n\n {/* Workspace Info */}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{workspace.name}</p>\n {workspace.slug ? (\n <p className=\"text-xs text-text-muted truncate\">/{workspace.slug}</p>\n ) : null}\n </div>\n\n {/* Selection Indicator */}\n {selectedWorkspace === workspace.id && (\n <CheckCircle className=\"size-5 flex-shrink-0 text-action-primary-bg\" />\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-default px-6 py-4\">\n {/* Already-installed info */}\n {installInfo && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-info-bg-subtle px-3 py-2 text-sm text-status-info-text\">\n <Info className=\"size-4 flex-shrink-0\" />\n <span>{installInfo}</span>\n </div>\n )}\n {/* Error message */}\n {installError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-error-bg-subtle/10 px-3 py-2 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <span>{installError}</span>\n </div>\n )}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={installing}\n className=\"cursor-pointer rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleInstall}\n disabled={!selectedWorkspace || installing}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2\"\n >\n {installing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Installing…\n </>\n ) : (\n 'Install App'\n )}\n </button>\n </div>\n </div>\n </dialog>\n </div>\n );\n};\n"],"mappings":";;;;;;AAiCA,IAAM,IAAuB,mGACvB,IAAgC,mOAChC,IAAiC,kJACjC,IAA+B,2RAC/B,IAAoC,6JACpC,IAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,EAEI,IAAiD;CACrD,mBAAmB;CACnB,gBAAgB;CAChB,mBAAmB;CACnB,WAAW;CACX,oBAAoB;CACpB,qBAAqB;CACrB,kBAAkB;CACnB;AAED,SAAS,EAAgB,GAAqB;AAC5C,MAAK,IAAM,CAAC,GAAM,MAAY,OAAO,QAAQ,EAAuB,CAClE,KAAI,EAAI,aAAa,CAAC,SAAS,EAAK,CAAE,QAAO;AAE/C,QAAO;;AAkBT,IAAM,IAAoC;CACxC,mBAAmB;CACnB,YAAY;CACZ,cAAc;CACd,aAAa;CACd;AAED,SAAS,EAAe,GAAqB,GAAqC;AAChF,SAAQ,EAAO,MAAf;EACE,KAAK,QACH,QAAO;EACT,KAAK,kBACH,QAAO;GAAE,GAAG;GAAO,mBAAmB,EAAO;GAAa;EAC5D,KAAK,eACH,QAAO;GAAE,GAAG;GAAO,YAAY;GAAM,cAAc;GAAM,aAAa;GAAM;EAC9E,KAAK,gBACH,QAAO;GAAE,GAAG;GAAO,YAAY;GAAO;EACxC,KAAK,WACH,QAAO;GAAE,GAAG;GAAO,cAAc,EAAO;GAAO;EACjD,KAAK,UACH,QAAO;GAAE,GAAG;GAAO,aAAa,EAAO;GAAM;EAC/C,QACE,QAAO;;;AAoCb,SAAS,EAAc,GAAoC;CACzD,IAAM,EAAE,iBAAc,GAAU,EAC1B,CAAC,GAAY,KAAiB,EAAsB,EAAE,CAAC,EACvD,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAEhD,IAAkB,EAAY,YAAY;AAC1C,UAGJ;GADA,EAAW,GAAK,EAChB,EAAS,KAAK;AAEd,OAAI;IACF,IAAM,IAAS,MAAM,EAA8C;KACjE,SAAS;KACT,OAAO;KACP,WAAW,KAAa,KAAA;KACzB,CAAC;AAEF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,gBAAgB;AAG/D,MAAc,EAAO,MAAM,cAAc,SAAS,EAAE,CAAC;YAC9C,GAAK;AACZ,MAAS,aAAe,QAAQ,IAAM,gBAAI,MAAM,6BAA6B,CAAC;aACtE;AACR,MAAW,GAAM;;;IAElB,CAAC,GAAW,EAAK,CAAC;AAMrB,QAJA,QAAgB;AACd,KAAiB;IAChB,CAAC,EAAgB,CAAC,EAEd;EACL;EACA;EACA;EACA,SAAS;EACV;;AAQH,IAAa,KAA6C,EACxD,WACA,YACA,QACA,YACA,mBAAgB,QAChB,qBACA,wBACI;CACJ,IAAM,EAAE,cAAW,YAAS,GAAU,EAChC,CAAC,GAAc,KAAmB,EAAW,GAAgB,EAAoB,EACjF,EAAE,sBAAmB,eAAY,iBAAc,mBAAgB,GAE/D,EAAE,eAAY,YAAS,aAAU,EAAc,CAAC,EAAO,EAGvD,IAAqB,EAAW,QAAQ,MAAO,EAAG,WAAW,SAAS,EAMtE,IAAyB,YAAY;AACzC,MAAI,CAAC,EAAmB;EAExB,IAAM,IAAY,EAAmB,MAAM,MAAO,EAAG,OAAO,EAAkB;AAC9E,MAAI,CAAC,EAAW;EAChB,IAAM,IAAc,GAAM;AAE1B,IAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEzC,MAAI;AACF,OAAI,CAAC,KAAW,MAAkB,aAAa;AAC7C,QAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;AAG7E,UAAM,EAAa;KACjB,SAAS;KACT,OAAO;KACP,WAAW,EACT,OAAO;MACL,WAAW,EAAI;MACf;MACA,aAAa,EAAU;MACvB,UAAU;OACR,QAAQ;OACR,aAAa,EAAI;OAClB;MACF,EACF;KACD,WAAW,KAAa,KAAA;KACzB,CAAC;;GAIJ,IAAM,IAAS,MAAM,EAclB;IACD,SAAS;IACT,OAAO;IACP,WAAW;KACT,SAAS,KAAW;KACpB,WAAW,EAAI;KACf,aAAa,EAAU;KACxB;IACD,WAAW,KAAa,KAAA;IACzB,CAAC;AAEF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,sBAAsB;GAGrE,IAAM,IAAgB,EAAO,MAAM;AAEnC,OAAI,GAAe,QACjB,CAAI,EAAc,mBAChB,EAAgB;IACd,MAAM;IACN,MAAM,EAAc,WAAW;IAChC,CAAC,IAEF,IAAmB,EAAU,IAAI,EAAU,KAAK,EAChD,GAAS;QAEN;IACL,IAAM,IAAS,GAAe,SAAS;AAGvC,IADA,EAAgB;KAAE,MAAM;KAAY,OADnB,EAAgB,EAAO;KACa,CAAC,EACtD,IAAqB,MAAM,EAAO,CAAC;;WAE9B,GAAK;GACZ,IAAM,IAAS,aAAe,QAAQ,EAAI,UAAU;AAGpD,GADA,EAAgB;IAAE,MAAM;IAAY,OADnB,EAAgB,EAAO;IACa,CAAC,EACtD,IAAqB,MAAM,EAAO,CAAC;YAC3B;AACR,KAAgB,EAAE,MAAM,iBAAiB,CAAC;;IAQxC,IAA4B,YAAY;AAC5C,MAAI,CAAC,EAAmB;EAExB,IAAM,IAAY,EAAmB,MAAM,MAAO,EAAG,OAAO,EAAkB;AACzE,SAEL;KAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEzC,OAAI;IAgBF,IAAM,IAAS,MAAM,EAElB;KACD,SAAS;KACT,OAAO;KACP,WAnBkB;MAClB,cAAc,CAAC,EAAU,GAAG;MAC5B,OAAO;OACL,SAAS,EAAI,QAAQ,EAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ,IAAI;OAChE,aAAa,EAAI;OACjB,SAAS,EAAI,WAAW;OACxB,MAAM,EAAI,QAAQ;OAClB,UAAU;QACR,WAAW,EAAI;QACf,MAAM,EAAI;QACX;OACF;MACF;KAQC,WAAW,KAAa,KAAA;KACxB,aAAa,EAAU;KACvB,gBAAgB;KACjB,CAAC;AAEF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,sBAAsB;IAGrE,IAAM,IAAgB,EAAO,MAAM;AACnC,QAAI,GAAe,gBAAgB,EAAc,eAAe,GAAG;AAEjE,SAAI;AACF,YAAM,EAAa;OACjB,SAAS;OACT,OAAO;OACP,WAAW,EACT,OAAO;QACL,WAAW,EAAI;QACf,aAAa,EAAU;QACvB,eAAe,EAAU;QACzB,eAAe,EAAU;QACzB,gBAAgB,EAAU;QAC1B,SAAS,EAAI,WAAW;QACxB,UAAU;SACR,WAAW,EAAI;SACf,MAAM,EAAI;SACV,MAAM,EAAI;SACV,MAAM,EAAI;SACX;QACF,EACF;OACD,WAAW,KAAa,KAAA;OACzB,CAAC;aACI;AAKR,KADA,IAAmB,EAAU,IAAI,EAAU,KAAK,EAChD,GAAS;WACJ;KACL,IAAM,IAAS,GAAe,UAAU,IAAI,SAAS;AAGrD,KADA,EAAgB;MAAE,MAAM;MAAY,OADnB,EAAgB,EAAO;MACa,CAAC,EACtD,IAAqB,MAAM,EAAO,CAAC;;YAE9B,GAAK;IACZ,IAAM,IAAS,aAAe,QAAQ,EAAI,UAAU;AAGpD,IADA,EAAgB;KAAE,MAAM;KAAY,OADnB,EAAgB,EAAO;KACa,CAAC,EACtD,IAAqB,MAAM,EAAO,CAAC;aAC3B;AACR,MAAgB,EAAE,MAAM,iBAAiB,CAAC;;;;AAqB9C,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,UAAD;GACE,MAAK;GACL,cAAW;GACX,WAAU;GACV,SAAS;GACT,CAAA,EAGF,kBAAC,UAAD;GACE,MAAA;GACA,cAAW;GACX,mBAAgB;GAChB,WAAU;aAJZ;IAOE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAI,OACH,kBAAC,OAAD;OAAK,KAAK,EAAI;OAAM,KAAK,EAAI;OAAM,WAAU;OAAoC,CAAA,GAEjF,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAI,KAAK,OAAO,EAAE;OACf,CAAA,EAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,IAAG;OAA0B,WAAU;iBAA0C;OAEhF,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAA2B,EAAI;OAAS,CAAA,CACjD,EAAA,CAAA,CACF;SACN,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,cAAW;MACX,WAAU;gBAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;MACjB,CAAA,CACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA2B;MAEpC,CAAA,EAGJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,WAAU,uCAAwC,CAAA;OACvD,CAAA,GACJ,IACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAa,WAAU,sCAAuC,CAAA;QAC9D,kBAAC,KAAD;SAAG,WAAU;mBAAiC;SAA6B,CAAA;QAC3E,kBAAC,KAAD;SAAG,WAAU;mBAAgC,EAAM;SAAY,CAAA;QAC3D;WACJ,EAAmB,WAAW,IAChC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,+BAAgC,CAAA,EACrD,kBAAC,KAAD;QAAG,WAAU;kBAA0B;QAA2B,CAAA,CAC9D;WAEN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAmB,KAAK,MACvB,kBAAC,UAAD;QACE,MAAK;QAEL,eACE,EAAgB;SAAE,MAAM;SAAmB,aAAa,EAAU;SAAI,CAAC;QAEzE,WAAW,uFACT,MAAsB,EAAU,KAC5B,4BACA;kBATR;SAaE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;UAC5B,CAAA;SAGN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAA0C,EAAU;WAAS,CAAA,EACzE,EAAU,OACT,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAAgD,KAAE,EAAU,KAAS;eACnE,KACA;;SAGL,MAAsB,EAAU,MAC/B,kBAAC,GAAD,EAAa,WAAU,+CAAgD,CAAA;SAElE;UA3BF,EAAU,GA2BR,CACT;OACE,CAAA;MAEJ,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,wBAAyB,CAAA,EACzC,kBAAC,QAAD,EAAA,UAAO,GAAmB,CAAA,CACtB;;MAGP,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,wBAAyB,CAAA,EAChD,kBAAC,QAAD,EAAA,UAAO,GAAoB,CAAA,CACvB;;MAER,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAU;kBACX;QAEQ,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,SAnJU,YAAY;SAChC,IAAM,IAAU,EAAI,MAAM,aAAa,IAAI;AAE3C,SAAI,MAAkB,eAAe,KAAW,EAAwB,IAAI,EAAQ,GAElF,MAAM,GAAwB,GAG9B,MAAM,GAA2B;;QA4IzB,UAAU,CAAC,KAAqB;QAChC,WAAU;kBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,EAAA,cAE1C,EAAA,CAAA,GAEH;QAEK,CAAA,CACL;;MACF;;IACC;KACL;MAvJY"}
|
|
@@ -3,9 +3,9 @@ import { useCart as t } from "../hooks/useCart.js";
|
|
|
3
3
|
import { formatNumber as n, formatPrice as r } from "../utils/index.js";
|
|
4
4
|
import { useCreateReview as ee, useDeleteReview as te, useMarkReviewHelpful as ne, useStoreProductDetails as re, useUpdateReview as ie } from "../hooks/useStoreGraphQL.js";
|
|
5
5
|
import { useInstallations as ae } from "../hooks/useInstallations.js";
|
|
6
|
-
import { nativeConfirm as oe, nativeImpact as se, nativeNotify as
|
|
7
|
-
import { InstallAppModal as
|
|
8
|
-
import { useCallback as
|
|
6
|
+
import { nativeConfirm as oe, nativeImpact as se, nativeNotify as i } from "../utils/nativeBridge.js";
|
|
7
|
+
import { InstallAppModal as ce } from "../components/InstallAppModal.js";
|
|
8
|
+
import { useCallback as a, useEffect as le, useMemo as ue, useRef as de, useState as o } from "react";
|
|
9
9
|
import { AlertCircle as s, ArrowLeft as fe, Check as pe, Loader2 as me, Package as he, ShieldCheck as c, ShoppingCart as ge, Star as l, ThumbsUp as u } from "lucide-react";
|
|
10
10
|
import { Fragment as _e, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
11
11
|
import { useLocation as ve, useNavigate as ye, useParams as be } from "react-router-dom";
|
|
@@ -448,13 +448,13 @@ var Se = (e, t) => ({
|
|
|
448
448
|
]
|
|
449
449
|
})
|
|
450
450
|
}), De = () => {
|
|
451
|
-
let { productId: s } = be(), u = ye(), De = ve(), { basePath: p, workspaceId: m } = e(), { addProduct: h, cartItems: Oe, addingToCart:
|
|
452
|
-
filter:
|
|
451
|
+
let { productId: s } = be(), u = ye(), De = ve(), { basePath: p, workspaceId: m } = e(), { addProduct: h, cartItems: Oe, addingToCart: ke, createOrder: Ae, checkingOut: je } = t(), { installations: Me, refetch: g } = ae({
|
|
452
|
+
filter: ue(() => s && m ? {
|
|
453
453
|
productId: s,
|
|
454
454
|
workspaceId: m
|
|
455
455
|
} : void 0, [s, m]),
|
|
456
456
|
limit: 1
|
|
457
|
-
}),
|
|
457
|
+
}), Ne = Me.some((e) => e.productId === s && e.workspaceId === m && e.status === "ACTIVE"), { createReview: _, loading: v } = ee(), { updateReview: y, loading: Pe } = ie(), { deleteReview: b, loading: Fe } = te(), { markHelpful: Ie, loading: Le } = ne(), x = xe(), [S, Re] = o(!1), [C, ze] = o(!1), [Be, w] = o(!1), [T, E] = o(null), [D, O] = o(null), [Ve, k] = o("success"), [A, j] = o(!1), [M, N] = o(""), [P, F] = o(""), [I, L] = o(5), [He, R] = o(null), [Ue, z] = o(!1), { data: We, loading: Ge, error: B, refetch: V } = re({
|
|
458
458
|
id: s,
|
|
459
459
|
slug: s,
|
|
460
460
|
includeReviews: !0,
|
|
@@ -462,16 +462,16 @@ var Se = (e, t) => ({
|
|
|
462
462
|
includeRelated: !0,
|
|
463
463
|
relatedLimit: 4,
|
|
464
464
|
includeVersions: !0
|
|
465
|
-
}), H =
|
|
465
|
+
}), H = We, U = H?.product, W = H?.publisher, G = Oe.some((e) => e.productId === U?.id), Ke = a(async () => {
|
|
466
466
|
if (U) {
|
|
467
|
-
if (
|
|
467
|
+
if (E(null), G) {
|
|
468
468
|
u(`${p}/cart`);
|
|
469
469
|
return;
|
|
470
470
|
}
|
|
471
471
|
try {
|
|
472
472
|
await h(Se(U, W), void 0, 1);
|
|
473
473
|
} catch (e) {
|
|
474
|
-
console.error("Failed to add product to cart:", e),
|
|
474
|
+
console.error("Failed to add product to cart:", e), E("Failed to add this product to cart. Please try again.");
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
}, [
|
|
@@ -481,11 +481,11 @@ var Se = (e, t) => ({
|
|
|
481
481
|
G,
|
|
482
482
|
u,
|
|
483
483
|
p
|
|
484
|
-
]),
|
|
484
|
+
]), qe = a(async () => {
|
|
485
485
|
if (U) {
|
|
486
|
-
|
|
486
|
+
E(null), w(!0);
|
|
487
487
|
try {
|
|
488
|
-
let e = U.price || 0, t = await
|
|
488
|
+
let e = U.price || 0, t = await Ae({
|
|
489
489
|
total: e,
|
|
490
490
|
lineItems: [{
|
|
491
491
|
productId: U.id,
|
|
@@ -498,95 +498,99 @@ var Se = (e, t) => ({
|
|
|
498
498
|
}],
|
|
499
499
|
billingAccountId: ""
|
|
500
500
|
});
|
|
501
|
-
t?.id ? u(`/billing/checkout/store/${t.id}${De.search}`) : (console.error("Failed to create order"),
|
|
501
|
+
t?.id ? u(`/billing/checkout/store/${t.id}${De.search}`) : (console.error("Failed to create order"), E("Failed to create order. Please try again."), w(!1));
|
|
502
502
|
} catch (e) {
|
|
503
|
-
console.error("Buy Now error:", e),
|
|
503
|
+
console.error("Buy Now error:", e), E(e instanceof Error ? e.message : "Failed to start checkout"), w(!1);
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
506
|
}, [
|
|
507
507
|
U,
|
|
508
|
-
|
|
508
|
+
Ae,
|
|
509
509
|
u
|
|
510
|
-
]),
|
|
511
|
-
U &&
|
|
512
|
-
}, [U]),
|
|
513
|
-
|
|
514
|
-
let
|
|
515
|
-
|
|
516
|
-
let e =
|
|
517
|
-
e &&
|
|
510
|
+
]), Je = a(() => {
|
|
511
|
+
U && z(!0);
|
|
512
|
+
}, [U]), Ye = de(U);
|
|
513
|
+
Ye.current = U;
|
|
514
|
+
let Xe = de(x);
|
|
515
|
+
Xe.current = x, le(() => {
|
|
516
|
+
let e = Ye.current;
|
|
517
|
+
e && Xe.current.emit("store.product.viewed", {
|
|
518
518
|
productId: e.id,
|
|
519
519
|
productSlug: e.slug,
|
|
520
520
|
productType: e.type,
|
|
521
521
|
pricingModel: e.pricingModel
|
|
522
522
|
});
|
|
523
523
|
}, [U?.id]);
|
|
524
|
-
let
|
|
525
|
-
U && (
|
|
524
|
+
let Ze = a((e, t) => {
|
|
525
|
+
U && (x.emit("store.product.installed", {
|
|
526
526
|
productId: U.id,
|
|
527
527
|
workspaceId: e
|
|
528
|
-
}), console.log(`Successfully installed ${U.name} to workspace ${t} (${e})`));
|
|
529
|
-
}, [
|
|
528
|
+
}), console.log(`Successfully installed ${U.name} to workspace ${t} (${e})`), g());
|
|
529
|
+
}, [
|
|
530
|
+
U,
|
|
531
|
+
x,
|
|
532
|
+
g
|
|
533
|
+
]), Qe = a((e) => {
|
|
530
534
|
console.error("Installation failed:", e.message);
|
|
531
|
-
}, []),
|
|
532
|
-
|
|
533
|
-
}, [H?.userReview]),
|
|
535
|
+
}, []), $e = a(() => {
|
|
536
|
+
N(H?.userReview?.title ?? ""), F(H?.userReview?.comment ?? ""), L(H?.userReview?.rating ?? 5), O(null), k("success"), j(!0);
|
|
537
|
+
}, [H?.userReview]), et = a(async () => {
|
|
534
538
|
if (!U) return;
|
|
535
|
-
if (
|
|
536
|
-
|
|
539
|
+
if (I < 1 || I > 5) {
|
|
540
|
+
k("error"), O("Choose a rating from 1 to 5 stars.");
|
|
537
541
|
return;
|
|
538
542
|
}
|
|
539
543
|
let e = {
|
|
540
544
|
productId: U.id,
|
|
541
|
-
rating:
|
|
542
|
-
title:
|
|
543
|
-
comment:
|
|
545
|
+
rating: I,
|
|
546
|
+
title: M.trim() || void 0,
|
|
547
|
+
comment: P.trim() || void 0
|
|
544
548
|
};
|
|
545
|
-
if (!(H?.userReview ? await
|
|
549
|
+
if (!(H?.userReview ? await y(H.userReview.id, {
|
|
546
550
|
rating: e.rating,
|
|
547
551
|
title: e.title,
|
|
548
552
|
comment: e.comment
|
|
549
|
-
}) : await
|
|
550
|
-
|
|
553
|
+
}) : await _(e))) {
|
|
554
|
+
k("error"), O("We could not save your review. Please try again.");
|
|
551
555
|
return;
|
|
552
556
|
}
|
|
553
|
-
|
|
557
|
+
k("success"), O(H?.userReview ? "Your review was updated." : "Your review was submitted."), j(!1), x.emit("store.review.submitted", {
|
|
554
558
|
productId: U.id,
|
|
555
|
-
rating:
|
|
559
|
+
rating: I
|
|
556
560
|
}), await V();
|
|
557
561
|
}, [
|
|
558
|
-
|
|
559
|
-
|
|
562
|
+
x,
|
|
563
|
+
_,
|
|
560
564
|
U,
|
|
561
565
|
H?.userReview,
|
|
562
566
|
V,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
]),
|
|
567
|
+
P,
|
|
568
|
+
I,
|
|
569
|
+
M,
|
|
570
|
+
y
|
|
571
|
+
]), tt = a(async () => {
|
|
568
572
|
if (H?.userReview && (se("medium"), await oe({
|
|
569
573
|
title: "Delete review",
|
|
570
574
|
message: "Are you sure you want to remove your review?",
|
|
571
575
|
okButtonTitle: "Delete",
|
|
572
576
|
cancelButtonTitle: "Cancel"
|
|
573
577
|
}) !== !1)) {
|
|
574
|
-
if (!await
|
|
575
|
-
|
|
578
|
+
if (!await b(H.userReview.id)) {
|
|
579
|
+
k("error"), O("We could not remove your review. Please try again."), i("error");
|
|
576
580
|
return;
|
|
577
581
|
}
|
|
578
|
-
|
|
582
|
+
k("success"), O("Your review was removed."), j(!1), N(""), F(""), L(5), i("success"), await V();
|
|
579
583
|
}
|
|
580
584
|
}, [
|
|
581
|
-
|
|
585
|
+
b,
|
|
582
586
|
H?.userReview,
|
|
583
587
|
V
|
|
584
|
-
]),
|
|
585
|
-
|
|
586
|
-
}, [
|
|
587
|
-
if (
|
|
588
|
-
if (
|
|
589
|
-
error:
|
|
588
|
+
]), nt = a(async (e) => {
|
|
589
|
+
R(e), await Ie(e) && await V(), R(null);
|
|
590
|
+
}, [Ie, V]);
|
|
591
|
+
if (Ge) return /* @__PURE__ */ d(Te, {});
|
|
592
|
+
if (B) return /* @__PURE__ */ d(Ee, {
|
|
593
|
+
error: B,
|
|
590
594
|
onRetry: V,
|
|
591
595
|
onBack: () => u(`${p}/marketplace`)
|
|
592
596
|
});
|
|
@@ -612,7 +616,7 @@ var Se = (e, t) => ({
|
|
|
612
616
|
]
|
|
613
617
|
})
|
|
614
618
|
});
|
|
615
|
-
let { reviews: K, reviewsCount:
|
|
619
|
+
let { reviews: K, reviewsCount: rt, relatedProducts: q, ratingDistribution: J, isPurchased: Y, userEntitlement: X } = H, Z = U, Q = J ? J.fiveStars + J.fourStars + J.threeStars + J.twoStars + J.oneStar : 0, it = J ? [
|
|
616
620
|
{
|
|
617
621
|
stars: 5,
|
|
618
622
|
count: J.fiveStars,
|
|
@@ -638,7 +642,7 @@ var Se = (e, t) => ({
|
|
|
638
642
|
count: J.oneStar,
|
|
639
643
|
percentage: Q > 0 ? J.oneStar / Q * 100 : 0
|
|
640
644
|
}
|
|
641
|
-
] : [],
|
|
645
|
+
] : [], at = Z.requiredPermissions || [], ot = Z.integrationDeps || [], st = () => Z.pricingModel === "FREE" ? "Free" : Z.pricingModel === "SUBSCRIPTION" ? `${r(Z.price, Z.currency)}/mo` : r(Z.price, Z.currency), $ = Z.pricingModel === "FREE" || Z.price === 0;
|
|
642
646
|
return /* @__PURE__ */ f("div", {
|
|
643
647
|
className: "h-full overflow-y-auto bg-bg-base",
|
|
644
648
|
children: [
|
|
@@ -707,7 +711,7 @@ var Se = (e, t) => ({
|
|
|
707
711
|
/* @__PURE__ */ d(l, { className: "size-4 fill-current text-status-warning-text" }),
|
|
708
712
|
/* @__PURE__ */ f("span", {
|
|
709
713
|
className: "ml-1 text-xs text-text-muted",
|
|
710
|
-
children: [n(
|
|
714
|
+
children: [n(rt || Z.reviewCount), " reviews"]
|
|
711
715
|
})
|
|
712
716
|
]
|
|
713
717
|
}),
|
|
@@ -733,14 +737,14 @@ var Se = (e, t) => ({
|
|
|
733
737
|
})
|
|
734
738
|
]
|
|
735
739
|
}),
|
|
736
|
-
|
|
740
|
+
T && /* @__PURE__ */ d("div", {
|
|
737
741
|
role: "alert",
|
|
738
742
|
className: "mb-4 rounded-lg border border-status-error-bg bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text",
|
|
739
|
-
children:
|
|
743
|
+
children: T
|
|
740
744
|
}),
|
|
741
745
|
/* @__PURE__ */ f("div", {
|
|
742
746
|
className: "mb-6 flex items-center gap-3",
|
|
743
|
-
children: [
|
|
747
|
+
children: [Ne ? /* @__PURE__ */ f("button", {
|
|
744
748
|
type: "button",
|
|
745
749
|
disabled: !0,
|
|
746
750
|
className: "flex items-center gap-2 rounded-full bg-status-success-bg-subtle px-8 py-3 font-medium text-status-success-text cursor-default",
|
|
@@ -757,26 +761,26 @@ var Se = (e, t) => ({
|
|
|
757
761
|
}), "Installed"]
|
|
758
762
|
}) : Y && !$ ? /* @__PURE__ */ d("button", {
|
|
759
763
|
type: "button",
|
|
760
|
-
onClick:
|
|
764
|
+
onClick: Je,
|
|
761
765
|
className: "cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90",
|
|
762
766
|
children: "Install to Workspace"
|
|
763
767
|
}) : $ ? /* @__PURE__ */ d("button", {
|
|
764
768
|
type: "button",
|
|
765
|
-
onClick:
|
|
769
|
+
onClick: Je,
|
|
766
770
|
className: "cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90",
|
|
767
771
|
children: "Install"
|
|
768
772
|
}) : /* @__PURE__ */ d("button", {
|
|
769
773
|
type: "button",
|
|
770
|
-
onClick:
|
|
771
|
-
disabled:
|
|
774
|
+
onClick: qe,
|
|
775
|
+
disabled: Be || je,
|
|
772
776
|
className: "cursor-pointer flex items-center gap-2 rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-70",
|
|
773
|
-
children:
|
|
777
|
+
children: Be || je ? /* @__PURE__ */ f(_e, { children: [/* @__PURE__ */ d(me, { className: "size-4 animate-spin" }), "Processing…"] }) : `Buy ${st()}`
|
|
774
778
|
}), !$ && !Y && /* @__PURE__ */ f("button", {
|
|
775
779
|
type: "button",
|
|
776
|
-
onClick:
|
|
777
|
-
disabled:
|
|
780
|
+
onClick: Ke,
|
|
781
|
+
disabled: ke,
|
|
778
782
|
className: "cursor-pointer flex items-center gap-2 rounded-full border border-border-default px-4 py-3 text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50",
|
|
779
|
-
children: [
|
|
783
|
+
children: [ke ? /* @__PURE__ */ d(me, { className: "size-4 animate-spin" }) : G ? /* @__PURE__ */ d(pe, { className: "size-4 text-status-success-text" }) : /* @__PURE__ */ d(ge, { className: "size-4" }), G ? "In Cart" : "Add to Cart"]
|
|
780
784
|
})]
|
|
781
785
|
}),
|
|
782
786
|
/* @__PURE__ */ f("p", {
|
|
@@ -850,13 +854,13 @@ var Se = (e, t) => ({
|
|
|
850
854
|
/* @__PURE__ */ f("div", {
|
|
851
855
|
className: "text-sm leading-relaxed text-text-secondary",
|
|
852
856
|
children: [/* @__PURE__ */ d("p", {
|
|
853
|
-
className:
|
|
857
|
+
className: S ? "" : "line-clamp-4",
|
|
854
858
|
children: Z.longDescription || Z.description || "No description available."
|
|
855
859
|
}), ((Z.longDescription || Z.description)?.length ?? 0) > 200 && /* @__PURE__ */ d("button", {
|
|
856
860
|
type: "button",
|
|
857
|
-
onClick: () =>
|
|
861
|
+
onClick: () => Re(!S),
|
|
858
862
|
className: "cursor-pointer mt-3 text-sm font-medium text-text-link",
|
|
859
|
-
children:
|
|
863
|
+
children: S ? "Show less" : "Show more"
|
|
860
864
|
})]
|
|
861
865
|
}),
|
|
862
866
|
Z.tags && Z.tags.length > 0 && /* @__PURE__ */ d("div", {
|
|
@@ -901,12 +905,12 @@ var Se = (e, t) => ({
|
|
|
901
905
|
}),
|
|
902
906
|
/* @__PURE__ */ f("p", {
|
|
903
907
|
className: "text-sm text-text-muted",
|
|
904
|
-
children: [n(
|
|
908
|
+
children: [n(rt || Z.reviewCount), " reviews"]
|
|
905
909
|
})
|
|
906
910
|
]
|
|
907
|
-
}),
|
|
911
|
+
}), it.length > 0 && /* @__PURE__ */ d("div", {
|
|
908
912
|
className: "flex-1 space-y-1.5",
|
|
909
|
-
children:
|
|
913
|
+
children: it.map((e) => /* @__PURE__ */ d(Ce, { ...e }, e.stars))
|
|
910
914
|
})]
|
|
911
915
|
}) : /* @__PURE__ */ f("div", {
|
|
912
916
|
className: "flex flex-col items-center justify-center py-6 text-center",
|
|
@@ -932,18 +936,18 @@ var Se = (e, t) => ({
|
|
|
932
936
|
]
|
|
933
937
|
}), /* @__PURE__ */ f("div", {
|
|
934
938
|
className: "mt-6 border-t border-border-default pt-4",
|
|
935
|
-
children: [
|
|
936
|
-
role:
|
|
937
|
-
className: `mb-3 rounded-lg px-3 py-2 text-sm ${
|
|
938
|
-
children:
|
|
939
|
+
children: [D && /* @__PURE__ */ d("p", {
|
|
940
|
+
role: Ve === "success" ? "status" : "alert",
|
|
941
|
+
className: `mb-3 rounded-lg px-3 py-2 text-sm ${Ve === "success" ? "bg-status-success-bg-subtle text-status-success-text" : "bg-status-error-bg-subtle text-status-error-text"}`,
|
|
942
|
+
children: D
|
|
939
943
|
}), Y || H?.userReview ? /* @__PURE__ */ f("div", {
|
|
940
944
|
className: "space-y-4",
|
|
941
945
|
children: [/* @__PURE__ */ d("button", {
|
|
942
946
|
type: "button",
|
|
943
|
-
onClick: () =>
|
|
947
|
+
onClick: () => A ? j(!1) : $e(),
|
|
944
948
|
className: "w-full cursor-pointer py-2 text-center text-sm font-medium text-text-primary transition-colors hover:text-text-link",
|
|
945
|
-
children: H?.userReview ?
|
|
946
|
-
}),
|
|
949
|
+
children: H?.userReview ? A ? "Cancel review editing" : "Edit your review" : A ? "Cancel review" : "Write a Review"
|
|
950
|
+
}), A && /* @__PURE__ */ f("div", {
|
|
947
951
|
className: "rounded-xl border border-border-default bg-bg-sunken p-4",
|
|
948
952
|
children: [
|
|
949
953
|
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
@@ -959,8 +963,8 @@ var Se = (e, t) => ({
|
|
|
959
963
|
5
|
|
960
964
|
].map((e) => /* @__PURE__ */ f("button", {
|
|
961
965
|
type: "button",
|
|
962
|
-
onClick: () =>
|
|
963
|
-
className: `cursor-pointer rounded-full border px-3 py-1.5 text-sm transition-colors ${
|
|
966
|
+
onClick: () => L(e),
|
|
967
|
+
className: `cursor-pointer rounded-full border px-3 py-1.5 text-sm transition-colors ${I === e ? "border-action-primary-border bg-action-primary-bg text-action-primary-text" : "border-border-default bg-bg-surface text-text-primary hover:bg-bg-surface"}`,
|
|
964
968
|
children: [e, " ★"]
|
|
965
969
|
}, e))
|
|
966
970
|
})] }),
|
|
@@ -968,8 +972,8 @@ var Se = (e, t) => ({
|
|
|
968
972
|
className: "mt-4 block text-sm font-medium text-text-primary",
|
|
969
973
|
children: ["Title", /* @__PURE__ */ d("input", {
|
|
970
974
|
"aria-label": "Review title",
|
|
971
|
-
value:
|
|
972
|
-
onChange: (e) =>
|
|
975
|
+
value: M,
|
|
976
|
+
onChange: (e) => N(e.target.value),
|
|
973
977
|
className: "mt-2 w-full rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-border",
|
|
974
978
|
placeholder: "Summarize your experience"
|
|
975
979
|
})]
|
|
@@ -978,8 +982,8 @@ var Se = (e, t) => ({
|
|
|
978
982
|
className: "mt-4 block text-sm font-medium text-text-primary",
|
|
979
983
|
children: ["Review", /* @__PURE__ */ d("textarea", {
|
|
980
984
|
"aria-label": "Review body",
|
|
981
|
-
value:
|
|
982
|
-
onChange: (e) =>
|
|
985
|
+
value: P,
|
|
986
|
+
onChange: (e) => F(e.target.value),
|
|
983
987
|
rows: 4,
|
|
984
988
|
required: !0,
|
|
985
989
|
"aria-required": "true",
|
|
@@ -991,16 +995,16 @@ var Se = (e, t) => ({
|
|
|
991
995
|
className: "mt-4 flex flex-wrap gap-3",
|
|
992
996
|
children: [/* @__PURE__ */ d("button", {
|
|
993
997
|
type: "button",
|
|
994
|
-
onClick:
|
|
995
|
-
disabled:
|
|
998
|
+
onClick: et,
|
|
999
|
+
disabled: v || Pe,
|
|
996
1000
|
className: "cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60",
|
|
997
|
-
children:
|
|
1001
|
+
children: v || Pe ? "Saving..." : H?.userReview ? "Update Review" : "Submit Review"
|
|
998
1002
|
}), H?.userReview && /* @__PURE__ */ d("button", {
|
|
999
1003
|
type: "button",
|
|
1000
|
-
onClick:
|
|
1001
|
-
disabled:
|
|
1004
|
+
onClick: tt,
|
|
1005
|
+
disabled: Fe,
|
|
1002
1006
|
className: "cursor-pointer rounded-lg border border-status-error-border px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-60",
|
|
1003
|
-
children:
|
|
1007
|
+
children: Fe ? "Removing..." : "Delete Review"
|
|
1004
1008
|
})]
|
|
1005
1009
|
})
|
|
1006
1010
|
]
|
|
@@ -1015,16 +1019,16 @@ var Se = (e, t) => ({
|
|
|
1015
1019
|
className: "mt-4 space-y-3",
|
|
1016
1020
|
children: [/* @__PURE__ */ d("ul", {
|
|
1017
1021
|
className: "space-y-3",
|
|
1018
|
-
children: (
|
|
1022
|
+
children: (C ? K : K.slice(0, 2)).map((e) => /* @__PURE__ */ d("li", { children: /* @__PURE__ */ d(we, {
|
|
1019
1023
|
review: e,
|
|
1020
|
-
onMarkHelpful:
|
|
1021
|
-
markingHelpful:
|
|
1024
|
+
onMarkHelpful: nt,
|
|
1025
|
+
markingHelpful: Le && He === e.id
|
|
1022
1026
|
}) }, e.id))
|
|
1023
1027
|
}), K.length > 2 && /* @__PURE__ */ d("button", {
|
|
1024
1028
|
type: "button",
|
|
1025
|
-
onClick: () =>
|
|
1029
|
+
onClick: () => ze(!C),
|
|
1026
1030
|
className: "cursor-pointer text-sm font-medium text-text-link",
|
|
1027
|
-
children:
|
|
1031
|
+
children: C ? "Show less" : `Show all ${K.length} reviews`
|
|
1028
1032
|
})]
|
|
1029
1033
|
}),
|
|
1030
1034
|
(!K || K.length === 0) && /* @__PURE__ */ d("p", {
|
|
@@ -1033,14 +1037,14 @@ var Se = (e, t) => ({
|
|
|
1033
1037
|
})
|
|
1034
1038
|
]
|
|
1035
1039
|
}),
|
|
1036
|
-
|
|
1040
|
+
at.length > 0 && /* @__PURE__ */ f("section", {
|
|
1037
1041
|
className: "mb-8",
|
|
1038
1042
|
children: [/* @__PURE__ */ d("h2", {
|
|
1039
1043
|
className: "mb-4 text-lg font-medium text-text-primary",
|
|
1040
1044
|
children: "Required Permissions"
|
|
1041
1045
|
}), /* @__PURE__ */ d("div", {
|
|
1042
1046
|
className: "space-y-2",
|
|
1043
|
-
children:
|
|
1047
|
+
children: at.map((e) => /* @__PURE__ */ f("div", {
|
|
1044
1048
|
className: "flex items-center gap-3 rounded-lg bg-bg-surface px-4 py-3",
|
|
1045
1049
|
children: [/* @__PURE__ */ d(c, { className: "size-5 text-status-success-text" }), /* @__PURE__ */ d("span", {
|
|
1046
1050
|
className: "text-sm text-text-primary",
|
|
@@ -1049,14 +1053,14 @@ var Se = (e, t) => ({
|
|
|
1049
1053
|
}, e))
|
|
1050
1054
|
})]
|
|
1051
1055
|
}),
|
|
1052
|
-
|
|
1056
|
+
ot.length > 0 && /* @__PURE__ */ f("section", {
|
|
1053
1057
|
className: "mb-8",
|
|
1054
1058
|
children: [/* @__PURE__ */ d("h2", {
|
|
1055
1059
|
className: "mb-4 text-lg font-medium text-text-primary",
|
|
1056
1060
|
children: "Integrations"
|
|
1057
1061
|
}), /* @__PURE__ */ d("div", {
|
|
1058
1062
|
className: "flex flex-wrap gap-2",
|
|
1059
|
-
children:
|
|
1063
|
+
children: ot.map((e) => /* @__PURE__ */ d("span", {
|
|
1060
1064
|
className: "rounded-lg bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary",
|
|
1061
1065
|
children: e
|
|
1062
1066
|
}, e))
|
|
@@ -1218,9 +1222,9 @@ var Se = (e, t) => ({
|
|
|
1218
1222
|
})]
|
|
1219
1223
|
})
|
|
1220
1224
|
}),
|
|
1221
|
-
Z && /* @__PURE__ */ d(
|
|
1222
|
-
isOpen:
|
|
1223
|
-
onClose: () =>
|
|
1225
|
+
Z && /* @__PURE__ */ d(ce, {
|
|
1226
|
+
isOpen: Ue,
|
|
1227
|
+
onClose: () => z(!1),
|
|
1224
1228
|
app: {
|
|
1225
1229
|
id: Z.id,
|
|
1226
1230
|
name: Z.name,
|
|
@@ -1230,8 +1234,8 @@ var Se = (e, t) => ({
|
|
|
1230
1234
|
version: Z.manifestVersion
|
|
1231
1235
|
},
|
|
1232
1236
|
purchaseState: Y && !$ ? "purchased" : "free",
|
|
1233
|
-
onInstallSuccess:
|
|
1234
|
-
onInstallError:
|
|
1237
|
+
onInstallSuccess: Ze,
|
|
1238
|
+
onInstallError: Qe
|
|
1235
1239
|
})
|
|
1236
1240
|
]
|
|
1237
1241
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductDetailPage.js","names":[],"sources":["../../src/pages/ProductDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useParams, useNavigate, useLocation } from 'react-router-dom';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport {\n ArrowLeft,\n Star,\n ShieldCheck,\n Check,\n ThumbsUp,\n Package,\n AlertCircle,\n ShoppingCart,\n Loader2,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport {\n useCreateReview,\n useDeleteReview,\n useMarkReviewHelpful,\n useStoreProductDetails,\n useUpdateReview,\n} from '../hooks/useStoreGraphQL';\nimport { useCart } from '../hooks/useCart';\nimport { useInstallations } from '../hooks/useInstallations';\nimport { formatNumber, formatPrice } from '../utils';\nimport { InstallAppModal } from '../components/InstallAppModal';\nimport type {\n Product,\n ItemType,\n ProductType,\n PricingModel as CartPricingModel,\n ProductStatus,\n} from '../types';\n\n// ============================================================================\n// Type Definitions for GraphQL Response\n// ============================================================================\n\ninterface Publisher {\n id: string;\n userId?: string;\n name: string;\n email?: string;\n websiteUrl?: string;\n logoUrl?: string;\n bio?: string;\n isVerified: boolean;\n totalSales?: number;\n totalEarnings?: number;\n createdAt?: string;\n}\n\ninterface ProductReview {\n id: string;\n productId: string;\n userId: string;\n rating: number;\n title?: string;\n comment?: string;\n status: string;\n helpful: number;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface RelatedProduct {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type: string;\n price: number;\n pricingModel: string;\n rating?: number;\n reviewCount?: number;\n downloads: number;\n publisher?: {\n id: string;\n name: string;\n isVerified: boolean;\n };\n}\n\ninterface RatingDistribution {\n fiveStars: number;\n fourStars: number;\n threeStars: number;\n twoStars: number;\n oneStar: number;\n}\n\ninterface UserEntitlement {\n id: string;\n status: string;\n licenseKey?: string;\n expiresAt?: string;\n}\n\ninterface StoreProduct {\n id: string;\n name: string;\n slug?: string;\n type: string;\n nature: string;\n status: string;\n description?: string;\n longDescription?: string;\n pricingModel: string;\n price: number;\n currency: string;\n icon?: string;\n screenshots: string[];\n videoUrls?: string[];\n documentationUrl?: string;\n repositoryUrl?: string;\n category?: string;\n tags?: string[];\n featured: boolean;\n zipFileUrl?: string;\n downloads: number;\n viewCount?: number;\n rating?: number;\n reviewCount: number;\n publishedAt?: string;\n createdAt: string;\n updatedAt: string;\n // Manifest fields\n subType?: string;\n manifestVersion?: string;\n authorName?: string;\n license?: string;\n minPlatformVersion?: string;\n compatibleProducts?: string[];\n requiredPermissions?: string[];\n integrationDeps?: string[];\n // Physical product fields\n sku?: string;\n stockQuantity?: number;\n trackInventory?: boolean;\n requiresShipping?: boolean;\n weight?: number;\n weightUnit?: string;\n brand?: string;\n manifest?: Record<string, unknown>;\n publisher?: Publisher;\n}\n\ninterface StoreProductDetailsResponse {\n product: StoreProduct;\n publisher: Publisher;\n reviews: ProductReview[];\n reviewsCount: number;\n relatedProducts: RelatedProduct[];\n userReview?: ProductReview;\n isPurchased: boolean;\n userEntitlement?: UserEntitlement;\n ratingDistribution?: RatingDistribution;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert StoreProduct (GraphQL) to Product (Cart) type\n */\nconst convertToCartProduct = (storeProduct: StoreProduct, publisher?: Publisher): Product => {\n // Map GraphQL pricing model to cart pricing model\n const mapPricingModel = (model: string): CartPricingModel => {\n const mapping: Record<string, CartPricingModel> = {\n FREE: 'FREE',\n PAID_ONETIME: 'ONE_TIME',\n SUBSCRIPTION: 'SUBSCRIPTION',\n USAGE_BASED: 'USAGE_BASED',\n FREEMIUM: 'FREEMIUM',\n };\n return mapping[model] || 'ONE_TIME';\n };\n\n // Map product type\n const mapProductType = (type: string): ProductType => {\n const mapping: Record<string, ProductType> = {\n APP: 'STANDALONE_APP',\n BOTAPP: 'STANDALONE_APP',\n AGENT: 'STANDALONE_APP',\n WORKFLOW: 'WORKFLOW_TEMPLATE',\n TEMPLATE: 'WORKFLOW_TEMPLATE',\n INTEGRATION: 'API_INTEGRATION',\n EXTENSION: 'BROWSER_EXTENSION',\n THEME: 'UI_THEME',\n PLUGIN: 'WORKSPACE_MODULE',\n COMPONENT: 'WORKSPACE_MODULE',\n WIDGET: 'WORKSPACE_MODULE',\n VIBEMODULE: 'WORKSPACE_MODULE',\n CONSOLE: 'STANDALONE_APP',\n NODE: 'WORKSPACE_MODULE',\n };\n return mapping[type] || 'STANDALONE_APP';\n };\n\n // Map item type\n const mapItemType = (nature: string, type: string): ItemType => {\n if (nature === 'PHYSICAL') return 'PHYSICAL';\n const typeMapping: Record<string, ItemType> = {\n APP: 'APP',\n BOTAPP: 'APP',\n AGENT: 'APP',\n WORKFLOW: 'WORKFLOW',\n TEMPLATE: 'TEMPLATE',\n INTEGRATION: 'INTEGRATION',\n EXTENSION: 'EXTENSION',\n THEME: 'THEME',\n };\n return typeMapping[type] || 'APP';\n };\n\n return {\n id: storeProduct.id,\n publisherId: publisher?.id || '',\n publisher: {\n id: publisher?.id || '',\n name: publisher?.name || storeProduct.authorName || 'Unknown',\n displayName: publisher?.name || storeProduct.authorName || 'Unknown',\n email: publisher?.email || '',\n verified: publisher?.isVerified || false,\n totalProducts: 0,\n totalDownloads: 0,\n averageRating: 0,\n joinedAt: publisher?.createdAt || storeProduct.createdAt,\n website: publisher?.websiteUrl,\n logoUrl: publisher?.logoUrl,\n },\n name: storeProduct.name,\n slug: storeProduct.slug || storeProduct.id,\n displayName: storeProduct.name,\n description: storeProduct.longDescription || storeProduct.description || '',\n shortDescription: storeProduct.description || '',\n iconUrl: storeProduct.icon,\n screenshotUrls: storeProduct.screenshots || [],\n videoUrls: storeProduct.videoUrls || [],\n itemType: mapItemType(storeProduct.nature, storeProduct.type),\n type: mapProductType(storeProduct.type),\n tags: storeProduct.tags || [],\n pricingModel: mapPricingModel(storeProduct.pricingModel),\n basePrice: storeProduct.price,\n currency: storeProduct.currency || 'USD',\n variants: [],\n status: (storeProduct.status as ProductStatus) || 'PUBLISHED',\n featured: storeProduct.featured,\n verified: publisher?.isVerified || false,\n downloadCount: storeProduct.downloads,\n installCount: storeProduct.downloads,\n orderCount: 0,\n averageRating: storeProduct.rating || 0,\n reviewCount: storeProduct.reviewCount,\n createdAt: storeProduct.createdAt,\n updatedAt: storeProduct.updatedAt,\n publishedAt: storeProduct.publishedAt,\n };\n};\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\n/**\n * Rating bar component for rating distribution\n */\nconst RatingBar: FC<{ stars: number; percentage: number; count: number }> = ({\n stars,\n percentage,\n count,\n}) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"w-3 text-xs text-text-muted\">{stars}</span>\n <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg-subtle\"\n style={{ width: `${percentage}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs text-text-muted\">{formatNumber(count)}</span>\n </div>\n);\n\n/**\n * Review card component\n */\nconst ReviewCard: FC<{\n review: ProductReview;\n onMarkHelpful: (reviewId: string) => void;\n markingHelpful: boolean;\n}> = ({ review, onMarkHelpful, markingHelpful }) => {\n const formatDate = (dateString: string) => {\n try {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n } catch {\n return dateString;\n }\n };\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-full bg-bg-sunken text-text-muted\">\n {review.userId.charAt(0).toUpperCase()}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">User {review.userId.slice(0, 8)}</p>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star\n key={star}\n className={`size-3 ${\n star <= review.rating\n ? 'fill-current text-status-warning-text'\n : 'text-text-muted'\n }`}\n />\n ))}\n </div>\n <span className=\"text-xs text-text-muted\">{formatDate(review.createdAt)}</span>\n </div>\n </div>\n </div>\n </div>\n {review.title && <h4 className=\"mb-2 font-medium text-text-primary\">{review.title}</h4>}\n {review.comment && <p className=\"mb-3 text-sm text-text-muted\">{review.comment}</p>}\n <div className=\"flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => onMarkHelpful(review.id)}\n disabled={markingHelpful}\n aria-label={`Mark review from ${review.userId} as helpful`}\n className=\"flex items-center gap-1 text-xs text-text-muted transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <ThumbsUp className=\"size-3.5\" />\n <span>{markingHelpful ? 'Updating…' : `Helpful (${review.helpful})`}</span>\n </button>\n </div>\n </div>\n );\n};\n\n/**\n * Loading skeleton component - comprehensive skeleton matching full page layout\n */\nconst LoadingSkeleton: FC = () => (\n <div className=\"h-full overflow-y-auto\">\n {/* Header Skeleton */}\n <div className=\"sticky top-0 z-10 border-b border-border-default bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"h-6 w-48 flex-1 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"flex gap-2\">\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n <div className=\"mx-auto max-w-6xl p-6\">\n {/* App Header Section Skeleton */}\n <div className=\"mb-8 flex flex-col gap-6 md:flex-row\">\n {/* App Icon & Basic Info */}\n <div className=\"flex gap-4 md:w-1/2\">\n <div className=\"size-28 animate-pulse rounded-2xl bg-bg-sunken\" />\n <div className=\"flex flex-1 flex-col justify-center gap-2\">\n <div className=\"h-8 w-3/4 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"flex items-center gap-2\">\n <div className=\"h-4 w-32 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"size-4 animate-pulse rounded-full bg-bg-sunken\" />\n </div>\n <div className=\"flex gap-3\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-5 w-24 animate-pulse rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n {/* Rating & Install Skeleton */}\n <div className=\"flex flex-col gap-4 md:w-1/2 md:items-end\">\n <div className=\"flex items-center gap-4\">\n <div className=\"text-center\">\n <div className=\"h-10 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-20 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-12 w-px bg-border-default\" />\n <div className=\"text-center\">\n <div className=\"h-8 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-12 w-px bg-border-default\" />\n <div className=\"text-center\">\n <div className=\"h-8 w-20 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-12 animate-pulse rounded bg-bg-sunken\" />\n </div>\n </div>\n <div className=\"flex gap-3\">\n <div className=\"h-12 w-40 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"size-12 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n {/* Screenshots Skeleton */}\n <section className=\"mb-8\">\n <div className=\"aspect-video animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"mt-4 flex gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"h-20 w-36 animate-pulse rounded-lg bg-bg-sunken\" />\n ))}\n </div>\n </section>\n\n {/* About Section Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-32 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-3/4 rounded bg-bg-sunken\" />\n <div className=\"h-4 w-5/6 rounded bg-bg-sunken\" />\n </div>\n <div className=\"mt-4 flex gap-2\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"h-6 w-16 rounded-full bg-bg-sunken\" />\n ))}\n </div>\n </div>\n </section>\n\n {/* Ratings & Reviews Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-40 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-4 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"mb-6 flex flex-col gap-6 md:flex-row\">\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4 md:w-64\">\n <div className=\"flex items-center gap-6\">\n <div className=\"text-center\">\n <div className=\"h-12 w-16 rounded bg-bg-sunken\" />\n <div className=\"my-1 flex justify-center gap-0.5\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"size-4 rounded bg-bg-sunken\" />\n ))}\n </div>\n <div className=\"h-3 w-20 rounded bg-bg-sunken\" />\n </div>\n <div className=\"flex-1 space-y-1\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <div className=\"h-2 w-3 rounded bg-bg-sunken\" />\n <div className=\"h-2 flex-1 rounded-full bg-bg-sunken\" />\n <div className=\"h-2 w-8 rounded bg-bg-sunken\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n <div className=\"h-12 w-32 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n {/* Review Cards Skeleton */}\n <div className=\"space-y-4\">\n {[...Array(2)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\"\n >\n <div className=\"mb-3 flex items-start gap-3\">\n <div className=\"size-10 rounded-full bg-bg-sunken\" />\n <div className=\"flex-1\">\n <div className=\"h-4 w-32 rounded bg-bg-sunken\" />\n <div className=\"mt-1 flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[...Array(5)].map((_, j) => (\n <div key={j} className=\"size-3 rounded bg-bg-sunken\" />\n ))}\n </div>\n <div className=\"h-3 w-20 rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-2/3 rounded bg-bg-sunken\" />\n </div>\n </div>\n ))}\n </div>\n </section>\n\n {/* Additional Info Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-48 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n {[...Array(6)].map((_, i) => (\n <div key={i}>\n <div className=\"h-3 w-16 rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-5 w-24 rounded bg-bg-sunken\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n\n {/* Related Products Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-40 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-4 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"grid gap-4 md:grid-cols-4\">\n {[...Array(4)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-14 rounded-xl bg-bg-sunken\" />\n <div className=\"flex-1\">\n <div className=\"h-4 w-24 rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-16 rounded bg-bg-sunken\" />\n <div className=\"mt-1 flex items-center gap-2\">\n <div className=\"h-3 w-8 rounded bg-bg-sunken\" />\n <div className=\"h-3 w-12 rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </section>\n\n {/* Publisher Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 h-6 w-36 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 rounded-lg bg-bg-sunken\" />\n <div>\n <div className=\"flex items-center gap-2\">\n <div className=\"h-5 w-32 rounded bg-bg-sunken\" />\n <div className=\"size-4 rounded-full bg-bg-sunken\" />\n </div>\n <div className=\"mt-1 h-4 w-40 rounded bg-bg-sunken\" />\n </div>\n </div>\n <div className=\"mt-3 space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-2/3 rounded bg-bg-sunken\" />\n </div>\n </div>\n </section>\n </div>\n </div>\n);\n\n/**\n * Error display component\n */\nconst ErrorDisplay: FC<{ error: Error; onRetry: () => void; onBack: () => void }> = ({\n error,\n onRetry,\n onBack,\n}) => (\n <div className=\"flex h-full items-center justify-center\">\n <div className=\"text-center\">\n <AlertCircle className=\"mx-auto mb-4 size-12 text-status-error-text\" />\n <h2 className=\"mb-2 text-xl font-semibold text-text-primary\">Failed to load product</h2>\n <p className=\"mb-4 text-text-muted\">\n {error.message || 'An error occurred while loading the product details.'}\n </p>\n <div className=\"flex justify-center gap-3\">\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg border border-border-default px-4 py-2 text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Go Back\n </button>\n </div>\n </div>\n </div>\n);\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * Product Detail Page Component\n *\n * Displays comprehensive product details using real GraphQL data\n */\nexport const ProductDetailPage: FC = () => {\n const { productId } = useParams<{ productId: string }>();\n const navigate = useNavigate();\n const location = useLocation();\n const { basePath, workspaceId } = useStore();\n const { addProduct, cartItems, addingToCart, createOrder, checkingOut } = useCart();\n const installationsFilter = useMemo(\n () => (productId && workspaceId ? { productId, workspaceId } : undefined),\n [productId, workspaceId]\n );\n const { installations: myInstallations } = useInstallations({\n filter: installationsFilter,\n limit: 1,\n });\n const isInstalledInCurrentWorkspace = myInstallations.some(\n (i) => i.productId === productId && i.workspaceId === workspaceId && i.status === 'ACTIVE'\n );\n const { createReview, loading: creatingReview } = useCreateReview();\n const { updateReview, loading: updatingReview } = useUpdateReview();\n const { deleteReview, loading: deletingReview } = useDeleteReview();\n const { markHelpful, loading: markingHelpful } = useMarkReviewHelpful();\n const bus = useEventBus();\n\n const [showFullDescription, setShowFullDescription] = useState(false);\n const [showAllReviews, setShowAllReviews] = useState(false);\n const [buyingNow, setBuyingNow] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n const [reviewNotice, setReviewNotice] = useState<string | null>(null);\n const [reviewNoticeTone, setReviewNoticeTone] = useState<'success' | 'error'>('success');\n const [isReviewFormOpen, setIsReviewFormOpen] = useState(false);\n const [reviewTitle, setReviewTitle] = useState('');\n const [reviewComment, setReviewComment] = useState('');\n const [reviewRating, setReviewRating] = useState(5);\n const [helpfulReviewId, setHelpfulReviewId] = useState<string | null>(null);\n\n // Install modal state\n const [isInstallModalOpen, setIsInstallModalOpen] = useState(false);\n\n // Fetch product details using GraphQL\n const { data, loading, error, refetch } = useStoreProductDetails({\n id: productId,\n slug: productId, // Also try as slug\n includeReviews: true,\n reviewsLimit: 10,\n includeRelated: true,\n relatedLimit: 4,\n includeVersions: true,\n });\n\n // Cast data to typed response\n const productDetails = data as StoreProductDetailsResponse | null;\n const product = productDetails?.product;\n const publisher = productDetails?.publisher;\n\n // Check if product is already in cart (must be before any early returns)\n const isInCart = cartItems.some((item) => item.productId === product?.id);\n\n // Handle Add to Cart (must be before any early returns)\n const handleAddToCart = useCallback(async () => {\n if (!product) return;\n\n setActionError(null);\n\n if (isInCart) {\n navigate(`${basePath}/cart`);\n return;\n }\n\n try {\n const cartProduct = convertToCartProduct(product, publisher);\n // Add to backend cart via GraphQL API call\n // This will make a mutation to global-public-gateway:4004 → tenantmodule-store-svc:4017\n await addProduct(cartProduct, undefined, 1);\n // Cart drawer will open automatically after adding\n } catch (error) {\n console.error('Failed to add product to cart:', error);\n setActionError('Failed to add this product to cart. Please try again.');\n }\n }, [product, publisher, addProduct, isInCart, navigate, basePath]);\n\n // Handle Buy Now - creates an order and immediately proceeds to checkout\n const handleBuyNow = useCallback(async () => {\n if (!product) return;\n\n setActionError(null);\n setBuyingNow(true);\n\n try {\n // Get product price\n const price = product.price || 0;\n\n // Build line item for this product\n const lineItem = {\n productId: product.id,\n name: product.name,\n quantity: 1,\n unitPrice: price,\n subtotal: price,\n itemType: product.nature?.toLowerCase() || 'digital',\n pricingModel: product.pricingModel || 'PAID_ONETIME',\n };\n\n // Create order input\n const orderInput = {\n total: price,\n lineItems: [lineItem],\n billingAccountId: '', // Will be set on billing page\n };\n\n // Call createStoreOrder mutation to create PENDING order\n const order = await createOrder(orderInput);\n\n if (order?.id) {\n // Navigate to billing page, preserving org/workspace/tenant context params\n navigate(`/billing/checkout/store/${order.id}${location.search}`);\n } else {\n console.error('Failed to create order');\n setActionError('Failed to create order. Please try again.');\n setBuyingNow(false);\n }\n } catch (error) {\n console.error('Buy Now error:', error);\n const message = error instanceof Error ? error.message : 'Failed to start checkout';\n setActionError(message);\n setBuyingNow(false);\n }\n }, [product, createOrder, navigate]);\n\n // Handle Install Free - Open modal for workspace selection (must be before any early returns)\n const handleInstallFree = useCallback(() => {\n if (!product) return;\n setIsInstallModalOpen(true);\n }, [product]);\n\n // Emit product.viewed event once product data loads. Read product via ref so\n // we only emit on id transitions, not on every field change.\n const productRef = useRef(product);\n productRef.current = product;\n const busRef = useRef(bus);\n busRef.current = bus;\n useEffect(() => {\n const p = productRef.current;\n if (!p) return;\n (busRef.current as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.product.viewed',\n {\n productId: p.id,\n productSlug: p.slug,\n productType: p.type,\n pricingModel: p.pricingModel,\n }\n );\n }, [product?.id]);\n\n // Handle successful installation (must be before any early returns)\n const handleInstallSuccess = useCallback(\n (workspaceId: string, workspaceName: string) => {\n if (!product) return;\n (bus as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.product.installed',\n {\n productId: product.id,\n workspaceId,\n }\n );\n console.log(\n `Successfully installed ${product.name} to workspace ${workspaceName} (${workspaceId})`\n );\n // Modal closes itself on success\n },\n [product, bus]\n );\n\n // Handle installation error (must be before any early returns)\n const handleInstallError = useCallback((error: Error) => {\n console.error('Installation failed:', error.message);\n // Error is shown in the modal\n }, []);\n\n const openReviewForm = useCallback(() => {\n setReviewTitle(productDetails?.userReview?.title ?? '');\n setReviewComment(productDetails?.userReview?.comment ?? '');\n setReviewRating(productDetails?.userReview?.rating ?? 5);\n setReviewNotice(null);\n setReviewNoticeTone('success');\n setIsReviewFormOpen(true);\n }, [productDetails?.userReview]);\n\n const handleSubmitReview = useCallback(async () => {\n if (!product) {\n return;\n }\n\n if (reviewRating < 1 || reviewRating > 5) {\n setReviewNoticeTone('error');\n setReviewNotice('Choose a rating from 1 to 5 stars.');\n return;\n }\n\n const payload = {\n productId: product.id,\n rating: reviewRating,\n title: reviewTitle.trim() || undefined,\n comment: reviewComment.trim() || undefined,\n };\n\n const result = productDetails?.userReview\n ? await updateReview(productDetails.userReview.id, {\n rating: payload.rating,\n title: payload.title,\n comment: payload.comment,\n })\n : await createReview(payload);\n\n if (!result) {\n setReviewNoticeTone('error');\n setReviewNotice('We could not save your review. Please try again.');\n return;\n }\n\n setReviewNoticeTone('success');\n setReviewNotice(\n productDetails?.userReview ? 'Your review was updated.' : 'Your review was submitted.'\n );\n setIsReviewFormOpen(false);\n (bus as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.review.submitted',\n {\n productId: product.id,\n rating: reviewRating,\n }\n );\n await refetch();\n }, [\n bus,\n createReview,\n product,\n productDetails?.userReview,\n refetch,\n reviewComment,\n reviewRating,\n reviewTitle,\n updateReview,\n ]);\n\n const handleDeleteReview = useCallback(async () => {\n if (!productDetails?.userReview) {\n return;\n }\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete review',\n message: 'Are you sure you want to remove your review?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === false) {\n return;\n }\n\n const deleted = await deleteReview(productDetails.userReview.id);\n if (!deleted) {\n setReviewNoticeTone('error');\n setReviewNotice('We could not remove your review. Please try again.');\n void nativeNotify('error');\n return;\n }\n\n setReviewNoticeTone('success');\n setReviewNotice('Your review was removed.');\n setIsReviewFormOpen(false);\n setReviewTitle('');\n setReviewComment('');\n setReviewRating(5);\n void nativeNotify('success');\n await refetch();\n }, [deleteReview, productDetails?.userReview, refetch]);\n\n const handleMarkHelpful = useCallback(\n async (reviewId: string) => {\n setHelpfulReviewId(reviewId);\n const result = await markHelpful(reviewId);\n if (result) {\n await refetch();\n }\n setHelpfulReviewId(null);\n },\n [markHelpful, refetch]\n );\n\n // Handle loading state\n if (loading) {\n return <LoadingSkeleton />;\n }\n\n // Handle error state\n if (error) {\n return (\n <ErrorDisplay\n error={error}\n onRetry={refetch}\n onBack={() => navigate(`${basePath}/marketplace`)}\n />\n );\n }\n\n // Handle not found state\n if (!productDetails?.product) {\n return (\n <div className=\"flex h-full items-center justify-center\">\n <div className=\"text-center\">\n <h2 className=\"mb-2 text-xl font-semibold text-text-primary\">Product not found</h2>\n <p className=\"mb-4 text-text-muted\">\n The product you are looking for does not exist or has been removed.\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Back to Marketplace\n </button>\n </div>\n </div>\n );\n }\n\n const {\n reviews,\n reviewsCount,\n relatedProducts,\n ratingDistribution,\n isPurchased,\n userEntitlement,\n } = productDetails;\n\n // Type assertion: we know product is defined because we checked !productDetails?.product above\n const safeProduct = product!;\n\n // Calculate rating distribution percentages\n const totalRatings = ratingDistribution\n ? ratingDistribution.fiveStars +\n ratingDistribution.fourStars +\n ratingDistribution.threeStars +\n ratingDistribution.twoStars +\n ratingDistribution.oneStar\n : 0;\n\n const ratingDistributionData = ratingDistribution\n ? [\n {\n stars: 5,\n count: ratingDistribution.fiveStars,\n percentage: totalRatings > 0 ? (ratingDistribution.fiveStars / totalRatings) * 100 : 0,\n },\n {\n stars: 4,\n count: ratingDistribution.fourStars,\n percentage: totalRatings > 0 ? (ratingDistribution.fourStars / totalRatings) * 100 : 0,\n },\n {\n stars: 3,\n count: ratingDistribution.threeStars,\n percentage: totalRatings > 0 ? (ratingDistribution.threeStars / totalRatings) * 100 : 0,\n },\n {\n stars: 2,\n count: ratingDistribution.twoStars,\n percentage: totalRatings > 0 ? (ratingDistribution.twoStars / totalRatings) * 100 : 0,\n },\n {\n stars: 1,\n count: ratingDistribution.oneStar,\n percentage: totalRatings > 0 ? (ratingDistribution.oneStar / totalRatings) * 100 : 0,\n },\n ]\n : [];\n\n // Parse permissions from manifest or requiredPermissions\n const permissions = safeProduct.requiredPermissions || [];\n\n // Parse integrations from integrationDeps\n const integrations = safeProduct.integrationDeps || [];\n\n // Pricing display\n const getPriceDisplay = () => {\n if (safeProduct.pricingModel === 'FREE') return 'Free';\n if (safeProduct.pricingModel === 'SUBSCRIPTION') {\n return `${formatPrice(safeProduct.price, safeProduct.currency)}/mo`;\n }\n return formatPrice(safeProduct.price, safeProduct.currency);\n };\n\n // Check if product is free\n const isFree = safeProduct.pricingModel === 'FREE' || safeProduct.price === 0;\n\n return (\n <div className=\"h-full overflow-y-auto bg-bg-base\">\n {/* Hero Section - Two Column Layout */}\n <div className=\"relative bg-bg-surface\">\n <div className=\"mx-auto max-w-7xl\">\n <div className=\"flex flex-col lg:flex-row\">\n {/* Left Column - Product Info */}\n <div className=\"flex-1 px-6 py-8 lg:max-w-xl lg:py-12\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"mb-6 cursor-pointer inline-flex items-center gap-2 text-sm text-text-muted transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n\n {/* App Title - Large Typography */}\n <h1 className=\"mb-4 text-4xl font-normal leading-tight text-text-primary lg:text-5xl\">\n {safeProduct.name}\n </h1>\n\n {/* Publisher */}\n <div className=\"mb-2\">\n {publisher?.websiteUrl ? (\n <a\n href={publisher.websiteUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-base font-medium text-text-link hover:underline\"\n >\n {publisher.name}\n </a>\n ) : (\n <span className=\"text-base font-medium text-text-primary\">\n {publisher?.name || safeProduct.authorName || 'Unknown Publisher'}\n </span>\n )}\n {publisher?.isVerified && (\n <ShieldCheck className=\"ml-1.5 inline size-4 text-status-success-text\" />\n )}\n </div>\n\n {/* Metadata */}\n <p className=\"mb-6 text-sm text-text-muted\">\n {safeProduct.category && <span>{safeProduct.category}</span>}\n {safeProduct.category && ' · '}\n {formatNumber(safeProduct.downloads)}+ downloads\n </p>\n\n {/* Stats Row */}\n <div className=\"mb-6 flex items-center gap-4\">\n {/* App Icon Badge */}\n <div className=\"flex size-12 items-center justify-center overflow-hidden rounded-xl bg-bg-sunken\">\n {safeProduct.icon ? (\n <img src={safeProduct.icon} alt=\"\" className=\"size-full object-cover\" />\n ) : (\n <span className=\"text-lg font-bold text-text-muted\">\n {safeProduct.name.charAt(0)}\n </span>\n )}\n </div>\n\n {/* Rating */}\n <div className=\"flex items-center gap-1 border-l border-border-default pl-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {(safeProduct.rating || 0).toFixed(1)}\n </span>\n <Star className=\"size-4 fill-current text-status-warning-text\" />\n <span className=\"ml-1 text-xs text-text-muted\">\n {formatNumber(reviewsCount || safeProduct.reviewCount)} reviews\n </span>\n </div>\n\n {/* Downloads */}\n <div className=\"border-l border-border-default pl-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {formatNumber(safeProduct.downloads)}+\n </span>\n <p className=\"text-xs text-text-muted\">Downloads</p>\n </div>\n\n {/* Type Badge */}\n <div className=\"border-l border-border-default pl-4\">\n <span className=\"rounded bg-bg-sunken px-2 py-1 text-xs font-medium text-text-primary\">\n {safeProduct.type}\n </span>\n <p className=\"mt-0.5 text-xs text-text-muted\">Type</p>\n </div>\n </div>\n\n {/* Action Buttons Row */}\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-bg bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n <div className=\"mb-6 flex items-center gap-3\">\n {isInstalledInCurrentWorkspace ? (\n <button\n type=\"button\"\n disabled\n className=\"flex items-center gap-2 rounded-full bg-status-success-bg-subtle px-8 py-3 font-medium text-status-success-text cursor-default\"\n >\n <svg\n className=\"size-4\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"2,8 6,12 14,4\" />\n </svg>\n Installed\n </button>\n ) : isPurchased && !isFree ? (\n <button\n type=\"button\"\n onClick={handleInstallFree}\n className=\"cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Install to Workspace\n </button>\n ) : isFree ? (\n <button\n type=\"button\"\n onClick={handleInstallFree}\n className=\"cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Install\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleBuyNow}\n disabled={buyingNow || checkingOut}\n className=\"cursor-pointer flex items-center gap-2 rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-70\"\n >\n {buyingNow || checkingOut ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n `Buy ${getPriceDisplay()}`\n )}\n </button>\n )}\n\n {/* Add to Cart / Wishlist */}\n {!isFree && !isPurchased && (\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n className=\"cursor-pointer flex items-center gap-2 rounded-full border border-border-default px-4 py-3 text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n {addingToCart ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : isInCart ? (\n <Check className=\"size-4 text-status-success-text\" />\n ) : (\n <ShoppingCart className=\"size-4\" />\n )}\n {isInCart ? 'In Cart' : 'Add to Cart'}\n </button>\n )}\n </div>\n\n {/* Info Text */}\n <p className=\"flex items-center gap-2 text-xs text-text-muted\">\n <Package className=\"size-4\" />\n This app is available for your workspace\n </p>\n\n {/* License Status */}\n {userEntitlement && (\n <p className=\"mt-2 flex items-center gap-2 text-xs text-text-muted\">\n <ShieldCheck className=\"size-4 text-status-success-text\" />\n License: {userEntitlement.status}\n {userEntitlement.expiresAt &&\n ` (Expires: ${new Date(userEntitlement.expiresAt).toLocaleDateString()})`}\n </p>\n )}\n </div>\n\n {/* Right Column - Hero Screenshot */}\n <div className=\"relative flex-1 bg-bg-sunken lg:min-h-[500px]\">\n {safeProduct.screenshots && safeProduct.screenshots.length > 0 ? (\n <img\n src={safeProduct.screenshots[0]}\n alt={`${safeProduct.name} screenshot`}\n className=\"size-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full min-h-[300px] items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 flex size-24 items-center justify-center rounded-2xl bg-bg-surface text-4xl font-bold text-text-muted\">\n {safeProduct.name.charAt(0)}\n </div>\n <p className=\"text-sm text-text-muted\">No preview available</p>\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Main Content Area */}\n <main className=\"mx-auto max-w-7xl px-6 py-8\">\n <div className=\"flex flex-col gap-8 lg:flex-row\">\n {/* Left Content */}\n <div className=\"flex-1 lg:max-w-3xl\">\n {/* Screenshots Gallery */}\n {safeProduct.screenshots && safeProduct.screenshots.length > 1 && (\n <section className=\"mb-8\">\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {safeProduct.screenshots.map((screenshot, index) => (\n <div\n key={index}\n className=\"relative h-64 w-96 flex-shrink-0 overflow-hidden rounded-xl bg-bg-sunken\"\n >\n <img\n src={screenshot}\n alt={`Screenshot ${index + 1}`}\n className=\"size-full object-cover\"\n />\n </div>\n ))}\n </div>\n </section>\n )}\n\n {/* About this app */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <h2 className=\"text-lg font-medium text-text-primary\">About this app</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-text-secondary\">\n <p className={!showFullDescription ? 'line-clamp-4' : ''}>\n {safeProduct.longDescription ||\n safeProduct.description ||\n 'No description available.'}\n </p>\n {((safeProduct.longDescription || safeProduct.description)?.length ?? 0) > 200 && (\n <button\n type=\"button\"\n onClick={() => setShowFullDescription(!showFullDescription)}\n className=\"cursor-pointer mt-3 text-sm font-medium text-text-link\"\n >\n {showFullDescription ? 'Show less' : 'Show more'}\n </button>\n )}\n </div>\n {/* Tags */}\n {safeProduct.tags && safeProduct.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {safeProduct.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full border border-border-default px-3 py-1.5 text-xs text-text-muted transition-colors hover:bg-bg-sunken\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </section>\n\n {/* Ratings & Reviews */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <h2 className=\"text-lg font-medium text-text-primary\">Ratings & Reviews</h2>\n </div>\n\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-5\">\n {reviews && reviews.length > 0 ? (\n <div className=\"flex items-start gap-8\">\n {/* Rating Score */}\n <div className=\"text-center\">\n <p className=\"text-5xl font-light text-text-primary\">\n {(safeProduct.rating || 0).toFixed(1)}\n </p>\n <div className=\"my-2 flex justify-center gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star\n key={star}\n className={`size-4 ${\n star <= Math.round(safeProduct.rating || 0)\n ? 'fill-current text-status-warning-text'\n : 'text-text-muted'\n }`}\n />\n ))}\n </div>\n <p className=\"text-sm text-text-muted\">\n {formatNumber(reviewsCount || safeProduct.reviewCount)} reviews\n </p>\n </div>\n\n {/* Rating Distribution */}\n {ratingDistributionData.length > 0 && (\n <div className=\"flex-1 space-y-1.5\">\n {ratingDistributionData.map((item) => (\n <RatingBar key={item.stars} {...item} />\n ))}\n </div>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"mb-3 flex gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star key={star} className=\"size-5 text-border-default\" />\n ))}\n </div>\n <p className=\"font-medium text-text-primary\">No reviews yet</p>\n <p className=\"mt-1 text-sm text-text-muted\">\n Be the first to share your experience with this app.\n </p>\n </div>\n )}\n\n {/* Write Review */}\n <div className=\"mt-6 border-t border-border-default pt-4\">\n {reviewNotice && (\n <p\n role={reviewNoticeTone === 'success' ? 'status' : 'alert'}\n className={`mb-3 rounded-lg px-3 py-2 text-sm ${\n reviewNoticeTone === 'success'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-error-bg-subtle text-status-error-text'\n }`}\n >\n {reviewNotice}\n </p>\n )}\n {isPurchased || productDetails?.userReview ? (\n <div className=\"space-y-4\">\n <button\n type=\"button\"\n onClick={() =>\n isReviewFormOpen ? setIsReviewFormOpen(false) : openReviewForm()\n }\n className=\"w-full cursor-pointer py-2 text-center text-sm font-medium text-text-primary transition-colors hover:text-text-link\"\n >\n {productDetails?.userReview\n ? isReviewFormOpen\n ? 'Cancel review editing'\n : 'Edit your review'\n : isReviewFormOpen\n ? 'Cancel review'\n : 'Write a Review'}\n </button>\n\n {isReviewFormOpen && (\n <div className=\"rounded-xl border border-border-default bg-bg-sunken p-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Your rating</p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => setReviewRating(star)}\n className={`cursor-pointer rounded-full border px-3 py-1.5 text-sm transition-colors ${\n reviewRating === star\n ? 'border-action-primary-border bg-action-primary-bg text-action-primary-text'\n : 'border-border-default bg-bg-surface text-text-primary hover:bg-bg-surface'\n }`}\n >\n {star} ★\n </button>\n ))}\n </div>\n </div>\n\n <label className=\"mt-4 block text-sm font-medium text-text-primary\">\n Title\n <input\n aria-label=\"Review title\"\n value={reviewTitle}\n onChange={(event) => setReviewTitle(event.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-border\"\n placeholder=\"Summarize your experience\"\n />\n </label>\n\n <label className=\"mt-4 block text-sm font-medium text-text-primary\">\n Review\n <textarea\n aria-label=\"Review body\"\n value={reviewComment}\n onChange={(event) => setReviewComment(event.target.value)}\n rows={4}\n required\n aria-required=\"true\"\n className=\"mt-2 w-full rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-border\"\n placeholder=\"What worked well? What should improve?\"\n />\n </label>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={handleSubmitReview}\n disabled={creatingReview || updatingReview}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {creatingReview || updatingReview\n ? 'Saving...'\n : productDetails?.userReview\n ? 'Update Review'\n : 'Submit Review'}\n </button>\n {productDetails?.userReview && (\n <button\n type=\"button\"\n onClick={handleDeleteReview}\n disabled={deletingReview}\n className=\"cursor-pointer rounded-lg border border-status-error-border px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {deletingReview ? 'Removing...' : 'Delete Review'}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n ) : (\n <p className=\"text-center text-sm text-text-muted\">\n Purchase this app to submit a review.\n </p>\n )}\n </div>\n </div>\n\n {/* Reviews List */}\n {reviews && reviews.length > 0 && (\n <div className=\"mt-4 space-y-3\">\n <ul className=\"space-y-3\">\n {(showAllReviews ? reviews : reviews.slice(0, 2)).map((review) => (\n <li key={review.id}>\n <ReviewCard\n review={review}\n onMarkHelpful={handleMarkHelpful}\n markingHelpful={markingHelpful && helpfulReviewId === review.id}\n />\n </li>\n ))}\n </ul>\n {reviews.length > 2 && (\n <button\n type=\"button\"\n onClick={() => setShowAllReviews(!showAllReviews)}\n className=\"cursor-pointer text-sm font-medium text-text-link\"\n >\n {showAllReviews ? 'Show less' : `Show all ${reviews.length} reviews`}\n </button>\n )}\n </div>\n )}\n {(!reviews || reviews.length === 0) && (\n <p className=\"mt-4 text-sm text-text-muted\">\n No reviews yet. Be the first to review!\n </p>\n )}\n </section>\n\n {/* Permissions */}\n {permissions.length > 0 && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Required Permissions</h2>\n <div className=\"space-y-2\">\n {permissions.map((permission) => (\n <div\n key={permission}\n className=\"flex items-center gap-3 rounded-lg bg-bg-surface px-4 py-3\"\n >\n <ShieldCheck className=\"size-5 text-status-success-text\" />\n <span className=\"text-sm text-text-primary\">{permission}</span>\n </div>\n ))}\n </div>\n </section>\n )}\n\n {/* Integrations */}\n {integrations.length > 0 && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Integrations</h2>\n <div className=\"flex flex-wrap gap-2\">\n {integrations.map((integration) => (\n <span\n key={integration}\n className=\"rounded-lg bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary\"\n >\n {integration}\n </span>\n ))}\n </div>\n </section>\n )}\n\n {/* Additional Information */}\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Additional Information</h2>\n <div className=\"grid grid-cols-2 gap-6 md:grid-cols-3\">\n {safeProduct.manifestVersion && (\n <div>\n <p className=\"text-xs text-text-muted\">Version</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.manifestVersion}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-xs text-text-muted\">Type</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">{safeProduct.type}</p>\n </div>\n {safeProduct.publishedAt && (\n <div>\n <p className=\"text-xs text-text-muted\">Published</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {new Date(safeProduct.publishedAt).toLocaleDateString()}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-xs text-text-muted\">Updated</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {new Date(safeProduct.updatedAt).toLocaleDateString()}\n </p>\n </div>\n {safeProduct.license && (\n <div>\n <p className=\"text-xs text-text-muted\">License</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.license}\n </p>\n </div>\n )}\n {safeProduct.minPlatformVersion && (\n <div>\n <p className=\"text-xs text-text-muted\">Min Platform Version</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.minPlatformVersion}\n </p>\n </div>\n )}\n </div>\n </section>\n </div>\n\n {/* Right Sidebar */}\n <div className=\"w-full lg:w-80\">\n {/* App Support */}\n {(safeProduct.documentationUrl || safeProduct.repositoryUrl || publisher?.email) && (\n <div className=\"mb-6 rounded-xl border border-border-default bg-bg-surface p-4\">\n <h3 className=\"font-medium text-text-primary\">App support</h3>\n <div className=\"mt-3 space-y-2 text-sm\">\n {safeProduct.documentationUrl && (\n <a\n href={safeProduct.documentationUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"block text-text-link hover:underline\"\n >\n Documentation\n </a>\n )}\n {safeProduct.repositoryUrl && (\n <a\n href={safeProduct.repositoryUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"block text-text-link hover:underline\"\n >\n Source repository\n </a>\n )}\n {publisher?.email && (\n <a\n href={`mailto:${publisher.email}`}\n className=\"block text-text-link hover:underline\"\n >\n Contact publisher\n </a>\n )}\n </div>\n </div>\n )}\n\n {/* More by Publisher */}\n {publisher && (\n <div className=\"mb-6 rounded-xl border border-border-default bg-bg-surface p-4\">\n <h3 className=\"font-medium text-text-primary\">Publisher</h3>\n <p className=\"mt-2 text-sm text-text-primary\">{publisher.name}</p>\n {publisher.bio && <p className=\"mt-2 text-sm text-text-muted\">{publisher.bio}</p>}\n {publisher.websiteUrl && (\n <a\n href={publisher.websiteUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"mt-3 inline-block text-sm text-text-link hover:underline\"\n >\n Visit publisher website\n </a>\n )}\n </div>\n )}\n\n {/* Similar Apps */}\n {relatedProducts && relatedProducts.length > 0 && (\n <div>\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <span className=\"font-medium text-text-primary\">Similar apps</span>\n </div>\n <div className=\"space-y-3\">\n {relatedProducts.slice(0, 4).map((related) => (\n <button\n type=\"button\"\n key={related.id}\n onClick={() => navigate(`${basePath}/marketplace/product/${related.id}`)}\n className=\"flex w-full cursor-pointer items-center gap-3 rounded-lg p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n {related.icon ? (\n <img\n src={related.icon}\n alt={related.name}\n className=\"size-12 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex size-12 items-center justify-center rounded-xl bg-bg-sunken text-lg font-bold text-text-muted\">\n {related.name.charAt(0)}\n </div>\n )}\n <div className=\"flex-1 overflow-hidden\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {related.name}\n </p>\n <p className=\"text-xs text-text-muted\">{publisher?.name || 'Unknown'}</p>\n {(related.reviewCount ?? 0) > 0 && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-text-muted\">\n {(related.rating || 0).toFixed(1)}\n </span>\n <Star className=\"size-3 fill-current text-status-warning-text\" />\n </div>\n )}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n </main>\n\n {/* Install App Modal */}\n {safeProduct && (\n <InstallAppModal\n isOpen={isInstallModalOpen}\n onClose={() => setIsInstallModalOpen(false)}\n app={{\n id: safeProduct.id,\n name: safeProduct.name,\n slug: safeProduct.slug,\n icon: safeProduct.icon,\n type: safeProduct.type,\n version: safeProduct.manifestVersion,\n }}\n purchaseState={isPurchased && !isFree ? 'purchased' : 'free'}\n onInstallSuccess={handleInstallSuccess}\n onInstallError={handleInstallError}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductDetailPage.js","names":[],"sources":["../../src/pages/ProductDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useParams, useNavigate, useLocation } from 'react-router-dom';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport {\n ArrowLeft,\n Star,\n ShieldCheck,\n Check,\n ThumbsUp,\n Package,\n AlertCircle,\n ShoppingCart,\n Loader2,\n} from 'lucide-react';\nimport { useStore } from '../providers/StoreProvider';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport {\n useCreateReview,\n useDeleteReview,\n useMarkReviewHelpful,\n useStoreProductDetails,\n useUpdateReview,\n} from '../hooks/useStoreGraphQL';\nimport { useCart } from '../hooks/useCart';\nimport { useInstallations } from '../hooks/useInstallations';\nimport { formatNumber, formatPrice } from '../utils';\nimport { InstallAppModal } from '../components/InstallAppModal';\nimport type {\n Product,\n ItemType,\n ProductType,\n PricingModel as CartPricingModel,\n ProductStatus,\n} from '../types';\n\n// ============================================================================\n// Type Definitions for GraphQL Response\n// ============================================================================\n\ninterface Publisher {\n id: string;\n userId?: string;\n name: string;\n email?: string;\n websiteUrl?: string;\n logoUrl?: string;\n bio?: string;\n isVerified: boolean;\n totalSales?: number;\n totalEarnings?: number;\n createdAt?: string;\n}\n\ninterface ProductReview {\n id: string;\n productId: string;\n userId: string;\n rating: number;\n title?: string;\n comment?: string;\n status: string;\n helpful: number;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface RelatedProduct {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type: string;\n price: number;\n pricingModel: string;\n rating?: number;\n reviewCount?: number;\n downloads: number;\n publisher?: {\n id: string;\n name: string;\n isVerified: boolean;\n };\n}\n\ninterface RatingDistribution {\n fiveStars: number;\n fourStars: number;\n threeStars: number;\n twoStars: number;\n oneStar: number;\n}\n\ninterface UserEntitlement {\n id: string;\n status: string;\n licenseKey?: string;\n expiresAt?: string;\n}\n\ninterface StoreProduct {\n id: string;\n name: string;\n slug?: string;\n type: string;\n nature: string;\n status: string;\n description?: string;\n longDescription?: string;\n pricingModel: string;\n price: number;\n currency: string;\n icon?: string;\n screenshots: string[];\n videoUrls?: string[];\n documentationUrl?: string;\n repositoryUrl?: string;\n category?: string;\n tags?: string[];\n featured: boolean;\n zipFileUrl?: string;\n downloads: number;\n viewCount?: number;\n rating?: number;\n reviewCount: number;\n publishedAt?: string;\n createdAt: string;\n updatedAt: string;\n // Manifest fields\n subType?: string;\n manifestVersion?: string;\n authorName?: string;\n license?: string;\n minPlatformVersion?: string;\n compatibleProducts?: string[];\n requiredPermissions?: string[];\n integrationDeps?: string[];\n // Physical product fields\n sku?: string;\n stockQuantity?: number;\n trackInventory?: boolean;\n requiresShipping?: boolean;\n weight?: number;\n weightUnit?: string;\n brand?: string;\n manifest?: Record<string, unknown>;\n publisher?: Publisher;\n}\n\ninterface StoreProductDetailsResponse {\n product: StoreProduct;\n publisher: Publisher;\n reviews: ProductReview[];\n reviewsCount: number;\n relatedProducts: RelatedProduct[];\n userReview?: ProductReview;\n isPurchased: boolean;\n userEntitlement?: UserEntitlement;\n ratingDistribution?: RatingDistribution;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert StoreProduct (GraphQL) to Product (Cart) type\n */\nconst convertToCartProduct = (storeProduct: StoreProduct, publisher?: Publisher): Product => {\n // Map GraphQL pricing model to cart pricing model\n const mapPricingModel = (model: string): CartPricingModel => {\n const mapping: Record<string, CartPricingModel> = {\n FREE: 'FREE',\n PAID_ONETIME: 'ONE_TIME',\n SUBSCRIPTION: 'SUBSCRIPTION',\n USAGE_BASED: 'USAGE_BASED',\n FREEMIUM: 'FREEMIUM',\n };\n return mapping[model] || 'ONE_TIME';\n };\n\n // Map product type\n const mapProductType = (type: string): ProductType => {\n const mapping: Record<string, ProductType> = {\n APP: 'STANDALONE_APP',\n BOTAPP: 'STANDALONE_APP',\n AGENT: 'STANDALONE_APP',\n WORKFLOW: 'WORKFLOW_TEMPLATE',\n TEMPLATE: 'WORKFLOW_TEMPLATE',\n INTEGRATION: 'API_INTEGRATION',\n EXTENSION: 'BROWSER_EXTENSION',\n THEME: 'UI_THEME',\n PLUGIN: 'WORKSPACE_MODULE',\n COMPONENT: 'WORKSPACE_MODULE',\n WIDGET: 'WORKSPACE_MODULE',\n VIBEMODULE: 'WORKSPACE_MODULE',\n CONSOLE: 'STANDALONE_APP',\n NODE: 'WORKSPACE_MODULE',\n };\n return mapping[type] || 'STANDALONE_APP';\n };\n\n // Map item type\n const mapItemType = (nature: string, type: string): ItemType => {\n if (nature === 'PHYSICAL') return 'PHYSICAL';\n const typeMapping: Record<string, ItemType> = {\n APP: 'APP',\n BOTAPP: 'APP',\n AGENT: 'APP',\n WORKFLOW: 'WORKFLOW',\n TEMPLATE: 'TEMPLATE',\n INTEGRATION: 'INTEGRATION',\n EXTENSION: 'EXTENSION',\n THEME: 'THEME',\n };\n return typeMapping[type] || 'APP';\n };\n\n return {\n id: storeProduct.id,\n publisherId: publisher?.id || '',\n publisher: {\n id: publisher?.id || '',\n name: publisher?.name || storeProduct.authorName || 'Unknown',\n displayName: publisher?.name || storeProduct.authorName || 'Unknown',\n email: publisher?.email || '',\n verified: publisher?.isVerified || false,\n totalProducts: 0,\n totalDownloads: 0,\n averageRating: 0,\n joinedAt: publisher?.createdAt || storeProduct.createdAt,\n website: publisher?.websiteUrl,\n logoUrl: publisher?.logoUrl,\n },\n name: storeProduct.name,\n slug: storeProduct.slug || storeProduct.id,\n displayName: storeProduct.name,\n description: storeProduct.longDescription || storeProduct.description || '',\n shortDescription: storeProduct.description || '',\n iconUrl: storeProduct.icon,\n screenshotUrls: storeProduct.screenshots || [],\n videoUrls: storeProduct.videoUrls || [],\n itemType: mapItemType(storeProduct.nature, storeProduct.type),\n type: mapProductType(storeProduct.type),\n tags: storeProduct.tags || [],\n pricingModel: mapPricingModel(storeProduct.pricingModel),\n basePrice: storeProduct.price,\n currency: storeProduct.currency || 'USD',\n variants: [],\n status: (storeProduct.status as ProductStatus) || 'PUBLISHED',\n featured: storeProduct.featured,\n verified: publisher?.isVerified || false,\n downloadCount: storeProduct.downloads,\n installCount: storeProduct.downloads,\n orderCount: 0,\n averageRating: storeProduct.rating || 0,\n reviewCount: storeProduct.reviewCount,\n createdAt: storeProduct.createdAt,\n updatedAt: storeProduct.updatedAt,\n publishedAt: storeProduct.publishedAt,\n };\n};\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\n/**\n * Rating bar component for rating distribution\n */\nconst RatingBar: FC<{ stars: number; percentage: number; count: number }> = ({\n stars,\n percentage,\n count,\n}) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"w-3 text-xs text-text-muted\">{stars}</span>\n <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg-subtle\"\n style={{ width: `${percentage}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs text-text-muted\">{formatNumber(count)}</span>\n </div>\n);\n\n/**\n * Review card component\n */\nconst ReviewCard: FC<{\n review: ProductReview;\n onMarkHelpful: (reviewId: string) => void;\n markingHelpful: boolean;\n}> = ({ review, onMarkHelpful, markingHelpful }) => {\n const formatDate = (dateString: string) => {\n try {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n } catch {\n return dateString;\n }\n };\n\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-full bg-bg-sunken text-text-muted\">\n {review.userId.charAt(0).toUpperCase()}\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">User {review.userId.slice(0, 8)}</p>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star\n key={star}\n className={`size-3 ${\n star <= review.rating\n ? 'fill-current text-status-warning-text'\n : 'text-text-muted'\n }`}\n />\n ))}\n </div>\n <span className=\"text-xs text-text-muted\">{formatDate(review.createdAt)}</span>\n </div>\n </div>\n </div>\n </div>\n {review.title && <h4 className=\"mb-2 font-medium text-text-primary\">{review.title}</h4>}\n {review.comment && <p className=\"mb-3 text-sm text-text-muted\">{review.comment}</p>}\n <div className=\"flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={() => onMarkHelpful(review.id)}\n disabled={markingHelpful}\n aria-label={`Mark review from ${review.userId} as helpful`}\n className=\"flex items-center gap-1 text-xs text-text-muted transition-colors hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <ThumbsUp className=\"size-3.5\" />\n <span>{markingHelpful ? 'Updating…' : `Helpful (${review.helpful})`}</span>\n </button>\n </div>\n </div>\n );\n};\n\n/**\n * Loading skeleton component - comprehensive skeleton matching full page layout\n */\nconst LoadingSkeleton: FC = () => (\n <div className=\"h-full overflow-y-auto\">\n {/* Header Skeleton */}\n <div className=\"sticky top-0 z-10 border-b border-border-default bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"h-6 w-48 flex-1 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"flex gap-2\">\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"size-10 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n <div className=\"mx-auto max-w-6xl p-6\">\n {/* App Header Section Skeleton */}\n <div className=\"mb-8 flex flex-col gap-6 md:flex-row\">\n {/* App Icon & Basic Info */}\n <div className=\"flex gap-4 md:w-1/2\">\n <div className=\"size-28 animate-pulse rounded-2xl bg-bg-sunken\" />\n <div className=\"flex flex-1 flex-col justify-center gap-2\">\n <div className=\"h-8 w-3/4 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"flex items-center gap-2\">\n <div className=\"h-4 w-32 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"size-4 animate-pulse rounded-full bg-bg-sunken\" />\n </div>\n <div className=\"flex gap-3\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-5 w-24 animate-pulse rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n {/* Rating & Install Skeleton */}\n <div className=\"flex flex-col gap-4 md:w-1/2 md:items-end\">\n <div className=\"flex items-center gap-4\">\n <div className=\"text-center\">\n <div className=\"h-10 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-20 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-12 w-px bg-border-default\" />\n <div className=\"text-center\">\n <div className=\"h-8 w-16 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-12 w-px bg-border-default\" />\n <div className=\"text-center\">\n <div className=\"h-8 w-20 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-12 animate-pulse rounded bg-bg-sunken\" />\n </div>\n </div>\n <div className=\"flex gap-3\">\n <div className=\"h-12 w-40 animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"size-12 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n </div>\n </div>\n\n {/* Screenshots Skeleton */}\n <section className=\"mb-8\">\n <div className=\"aspect-video animate-pulse rounded-lg bg-bg-sunken\" />\n <div className=\"mt-4 flex gap-4\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"h-20 w-36 animate-pulse rounded-lg bg-bg-sunken\" />\n ))}\n </div>\n </section>\n\n {/* About Section Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-32 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-3/4 rounded bg-bg-sunken\" />\n <div className=\"h-4 w-5/6 rounded bg-bg-sunken\" />\n </div>\n <div className=\"mt-4 flex gap-2\">\n {[...Array(4)].map((_, i) => (\n <div key={i} className=\"h-6 w-16 rounded-full bg-bg-sunken\" />\n ))}\n </div>\n </div>\n </section>\n\n {/* Ratings & Reviews Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-40 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-4 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"mb-6 flex flex-col gap-6 md:flex-row\">\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4 md:w-64\">\n <div className=\"flex items-center gap-6\">\n <div className=\"text-center\">\n <div className=\"h-12 w-16 rounded bg-bg-sunken\" />\n <div className=\"my-1 flex justify-center gap-0.5\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"size-4 rounded bg-bg-sunken\" />\n ))}\n </div>\n <div className=\"h-3 w-20 rounded bg-bg-sunken\" />\n </div>\n <div className=\"flex-1 space-y-1\">\n {[...Array(5)].map((_, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <div className=\"h-2 w-3 rounded bg-bg-sunken\" />\n <div className=\"h-2 flex-1 rounded-full bg-bg-sunken\" />\n <div className=\"h-2 w-8 rounded bg-bg-sunken\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n <div className=\"h-12 w-32 animate-pulse rounded-lg bg-bg-sunken\" />\n </div>\n {/* Review Cards Skeleton */}\n <div className=\"space-y-4\">\n {[...Array(2)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\"\n >\n <div className=\"mb-3 flex items-start gap-3\">\n <div className=\"size-10 rounded-full bg-bg-sunken\" />\n <div className=\"flex-1\">\n <div className=\"h-4 w-32 rounded bg-bg-sunken\" />\n <div className=\"mt-1 flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[...Array(5)].map((_, j) => (\n <div key={j} className=\"size-3 rounded bg-bg-sunken\" />\n ))}\n </div>\n <div className=\"h-3 w-20 rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-2/3 rounded bg-bg-sunken\" />\n </div>\n </div>\n ))}\n </div>\n </section>\n\n {/* Additional Info Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-48 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n {[...Array(6)].map((_, i) => (\n <div key={i}>\n <div className=\"h-3 w-16 rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-5 w-24 rounded bg-bg-sunken\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n\n {/* Related Products Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-5 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"h-6 w-40 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"h-4 w-16 animate-pulse rounded bg-bg-sunken\" />\n </div>\n <div className=\"grid gap-4 md:grid-cols-4\">\n {[...Array(4)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-14 rounded-xl bg-bg-sunken\" />\n <div className=\"flex-1\">\n <div className=\"h-4 w-24 rounded bg-bg-sunken\" />\n <div className=\"mt-1 h-3 w-16 rounded bg-bg-sunken\" />\n <div className=\"mt-1 flex items-center gap-2\">\n <div className=\"h-3 w-8 rounded bg-bg-sunken\" />\n <div className=\"h-3 w-12 rounded bg-bg-sunken\" />\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </section>\n\n {/* Publisher Skeleton */}\n <section className=\"mb-8\">\n <div className=\"mb-4 h-6 w-36 animate-pulse rounded bg-bg-sunken\" />\n <div className=\"animate-pulse rounded-lg border border-border-default bg-bg-surface p-4\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 rounded-lg bg-bg-sunken\" />\n <div>\n <div className=\"flex items-center gap-2\">\n <div className=\"h-5 w-32 rounded bg-bg-sunken\" />\n <div className=\"size-4 rounded-full bg-bg-sunken\" />\n </div>\n <div className=\"mt-1 h-4 w-40 rounded bg-bg-sunken\" />\n </div>\n </div>\n <div className=\"mt-3 space-y-2\">\n <div className=\"h-4 w-full rounded bg-bg-sunken\" />\n <div className=\"h-4 w-2/3 rounded bg-bg-sunken\" />\n </div>\n </div>\n </section>\n </div>\n </div>\n);\n\n/**\n * Error display component\n */\nconst ErrorDisplay: FC<{ error: Error; onRetry: () => void; onBack: () => void }> = ({\n error,\n onRetry,\n onBack,\n}) => (\n <div className=\"flex h-full items-center justify-center\">\n <div className=\"text-center\">\n <AlertCircle className=\"mx-auto mb-4 size-12 text-status-error-text\" />\n <h2 className=\"mb-2 text-xl font-semibold text-text-primary\">Failed to load product</h2>\n <p className=\"mb-4 text-text-muted\">\n {error.message || 'An error occurred while loading the product details.'}\n </p>\n <div className=\"flex justify-center gap-3\">\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg border border-border-default px-4 py-2 text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Go Back\n </button>\n </div>\n </div>\n </div>\n);\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * Product Detail Page Component\n *\n * Displays comprehensive product details using real GraphQL data\n */\nexport const ProductDetailPage: FC = () => {\n const { productId } = useParams<{ productId: string }>();\n const navigate = useNavigate();\n const location = useLocation();\n const { basePath, workspaceId } = useStore();\n const { addProduct, cartItems, addingToCart, createOrder, checkingOut } = useCart();\n const installationsFilter = useMemo(\n () => (productId && workspaceId ? { productId, workspaceId } : undefined),\n [productId, workspaceId]\n );\n const { installations: myInstallations, refetch: refetchInstallations } = useInstallations({\n filter: installationsFilter,\n limit: 1,\n });\n const isInstalledInCurrentWorkspace = myInstallations.some(\n (i) => i.productId === productId && i.workspaceId === workspaceId && i.status === 'ACTIVE'\n );\n const { createReview, loading: creatingReview } = useCreateReview();\n const { updateReview, loading: updatingReview } = useUpdateReview();\n const { deleteReview, loading: deletingReview } = useDeleteReview();\n const { markHelpful, loading: markingHelpful } = useMarkReviewHelpful();\n const bus = useEventBus();\n\n const [showFullDescription, setShowFullDescription] = useState(false);\n const [showAllReviews, setShowAllReviews] = useState(false);\n const [buyingNow, setBuyingNow] = useState(false);\n const [actionError, setActionError] = useState<string | null>(null);\n const [reviewNotice, setReviewNotice] = useState<string | null>(null);\n const [reviewNoticeTone, setReviewNoticeTone] = useState<'success' | 'error'>('success');\n const [isReviewFormOpen, setIsReviewFormOpen] = useState(false);\n const [reviewTitle, setReviewTitle] = useState('');\n const [reviewComment, setReviewComment] = useState('');\n const [reviewRating, setReviewRating] = useState(5);\n const [helpfulReviewId, setHelpfulReviewId] = useState<string | null>(null);\n\n // Install modal state\n const [isInstallModalOpen, setIsInstallModalOpen] = useState(false);\n\n // Fetch product details using GraphQL\n const { data, loading, error, refetch } = useStoreProductDetails({\n id: productId,\n slug: productId, // Also try as slug\n includeReviews: true,\n reviewsLimit: 10,\n includeRelated: true,\n relatedLimit: 4,\n includeVersions: true,\n });\n\n // Cast data to typed response\n const productDetails = data as StoreProductDetailsResponse | null;\n const product = productDetails?.product;\n const publisher = productDetails?.publisher;\n\n // Check if product is already in cart (must be before any early returns)\n const isInCart = cartItems.some((item) => item.productId === product?.id);\n\n // Handle Add to Cart (must be before any early returns)\n const handleAddToCart = useCallback(async () => {\n if (!product) return;\n\n setActionError(null);\n\n if (isInCart) {\n navigate(`${basePath}/cart`);\n return;\n }\n\n try {\n const cartProduct = convertToCartProduct(product, publisher);\n // Add to backend cart via GraphQL API call\n // This will make a mutation to global-public-gateway:4004 → tenantmodule-store-svc:4017\n await addProduct(cartProduct, undefined, 1);\n // Cart drawer will open automatically after adding\n } catch (error) {\n console.error('Failed to add product to cart:', error);\n setActionError('Failed to add this product to cart. Please try again.');\n }\n }, [product, publisher, addProduct, isInCart, navigate, basePath]);\n\n // Handle Buy Now - creates an order and immediately proceeds to checkout\n const handleBuyNow = useCallback(async () => {\n if (!product) return;\n\n setActionError(null);\n setBuyingNow(true);\n\n try {\n // Get product price\n const price = product.price || 0;\n\n // Build line item for this product\n const lineItem = {\n productId: product.id,\n name: product.name,\n quantity: 1,\n unitPrice: price,\n subtotal: price,\n itemType: product.nature?.toLowerCase() || 'digital',\n pricingModel: product.pricingModel || 'PAID_ONETIME',\n };\n\n // Create order input\n const orderInput = {\n total: price,\n lineItems: [lineItem],\n billingAccountId: '', // Will be set on billing page\n };\n\n // Call createStoreOrder mutation to create PENDING order\n const order = await createOrder(orderInput);\n\n if (order?.id) {\n // Navigate to billing page, preserving org/workspace/tenant context params\n navigate(`/billing/checkout/store/${order.id}${location.search}`);\n } else {\n console.error('Failed to create order');\n setActionError('Failed to create order. Please try again.');\n setBuyingNow(false);\n }\n } catch (error) {\n console.error('Buy Now error:', error);\n const message = error instanceof Error ? error.message : 'Failed to start checkout';\n setActionError(message);\n setBuyingNow(false);\n }\n }, [product, createOrder, navigate]);\n\n // Handle Install Free - Open modal for workspace selection (must be before any early returns)\n const handleInstallFree = useCallback(() => {\n if (!product) return;\n setIsInstallModalOpen(true);\n }, [product]);\n\n // Emit product.viewed event once product data loads. Read product via ref so\n // we only emit on id transitions, not on every field change.\n const productRef = useRef(product);\n productRef.current = product;\n const busRef = useRef(bus);\n busRef.current = bus;\n useEffect(() => {\n const p = productRef.current;\n if (!p) return;\n (busRef.current as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.product.viewed',\n {\n productId: p.id,\n productSlug: p.slug,\n productType: p.type,\n pricingModel: p.pricingModel,\n }\n );\n }, [product?.id]);\n\n // Handle successful installation (must be before any early returns)\n const handleInstallSuccess = useCallback(\n (workspaceId: string, workspaceName: string) => {\n if (!product) return;\n (bus as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.product.installed',\n {\n productId: product.id,\n workspaceId,\n }\n );\n console.log(\n `Successfully installed ${product.name} to workspace ${workspaceName} (${workspaceId})`\n );\n void refetchInstallations();\n },\n [product, bus, refetchInstallations]\n );\n\n // Handle installation error (must be before any early returns)\n const handleInstallError = useCallback((error: Error) => {\n console.error('Installation failed:', error.message);\n // Error is shown in the modal\n }, []);\n\n const openReviewForm = useCallback(() => {\n setReviewTitle(productDetails?.userReview?.title ?? '');\n setReviewComment(productDetails?.userReview?.comment ?? '');\n setReviewRating(productDetails?.userReview?.rating ?? 5);\n setReviewNotice(null);\n setReviewNoticeTone('success');\n setIsReviewFormOpen(true);\n }, [productDetails?.userReview]);\n\n const handleSubmitReview = useCallback(async () => {\n if (!product) {\n return;\n }\n\n if (reviewRating < 1 || reviewRating > 5) {\n setReviewNoticeTone('error');\n setReviewNotice('Choose a rating from 1 to 5 stars.');\n return;\n }\n\n const payload = {\n productId: product.id,\n rating: reviewRating,\n title: reviewTitle.trim() || undefined,\n comment: reviewComment.trim() || undefined,\n };\n\n const result = productDetails?.userReview\n ? await updateReview(productDetails.userReview.id, {\n rating: payload.rating,\n title: payload.title,\n comment: payload.comment,\n })\n : await createReview(payload);\n\n if (!result) {\n setReviewNoticeTone('error');\n setReviewNotice('We could not save your review. Please try again.');\n return;\n }\n\n setReviewNoticeTone('success');\n setReviewNotice(\n productDetails?.userReview ? 'Your review was updated.' : 'Your review was submitted.'\n );\n setIsReviewFormOpen(false);\n (bus as unknown as { emit: (name: string, payload: unknown) => void }).emit(\n 'store.review.submitted',\n {\n productId: product.id,\n rating: reviewRating,\n }\n );\n await refetch();\n }, [\n bus,\n createReview,\n product,\n productDetails?.userReview,\n refetch,\n reviewComment,\n reviewRating,\n reviewTitle,\n updateReview,\n ]);\n\n const handleDeleteReview = useCallback(async () => {\n if (!productDetails?.userReview) {\n return;\n }\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete review',\n message: 'Are you sure you want to remove your review?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === false) {\n return;\n }\n\n const deleted = await deleteReview(productDetails.userReview.id);\n if (!deleted) {\n setReviewNoticeTone('error');\n setReviewNotice('We could not remove your review. Please try again.');\n void nativeNotify('error');\n return;\n }\n\n setReviewNoticeTone('success');\n setReviewNotice('Your review was removed.');\n setIsReviewFormOpen(false);\n setReviewTitle('');\n setReviewComment('');\n setReviewRating(5);\n void nativeNotify('success');\n await refetch();\n }, [deleteReview, productDetails?.userReview, refetch]);\n\n const handleMarkHelpful = useCallback(\n async (reviewId: string) => {\n setHelpfulReviewId(reviewId);\n const result = await markHelpful(reviewId);\n if (result) {\n await refetch();\n }\n setHelpfulReviewId(null);\n },\n [markHelpful, refetch]\n );\n\n // Handle loading state\n if (loading) {\n return <LoadingSkeleton />;\n }\n\n // Handle error state\n if (error) {\n return (\n <ErrorDisplay\n error={error}\n onRetry={refetch}\n onBack={() => navigate(`${basePath}/marketplace`)}\n />\n );\n }\n\n // Handle not found state\n if (!productDetails?.product) {\n return (\n <div className=\"flex h-full items-center justify-center\">\n <div className=\"text-center\">\n <h2 className=\"mb-2 text-xl font-semibold text-text-primary\">Product not found</h2>\n <p className=\"mb-4 text-text-muted\">\n The product you are looking for does not exist or has been removed.\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Back to Marketplace\n </button>\n </div>\n </div>\n );\n }\n\n const {\n reviews,\n reviewsCount,\n relatedProducts,\n ratingDistribution,\n isPurchased,\n userEntitlement,\n } = productDetails;\n\n // Type assertion: we know product is defined because we checked !productDetails?.product above\n const safeProduct = product!;\n\n // Calculate rating distribution percentages\n const totalRatings = ratingDistribution\n ? ratingDistribution.fiveStars +\n ratingDistribution.fourStars +\n ratingDistribution.threeStars +\n ratingDistribution.twoStars +\n ratingDistribution.oneStar\n : 0;\n\n const ratingDistributionData = ratingDistribution\n ? [\n {\n stars: 5,\n count: ratingDistribution.fiveStars,\n percentage: totalRatings > 0 ? (ratingDistribution.fiveStars / totalRatings) * 100 : 0,\n },\n {\n stars: 4,\n count: ratingDistribution.fourStars,\n percentage: totalRatings > 0 ? (ratingDistribution.fourStars / totalRatings) * 100 : 0,\n },\n {\n stars: 3,\n count: ratingDistribution.threeStars,\n percentage: totalRatings > 0 ? (ratingDistribution.threeStars / totalRatings) * 100 : 0,\n },\n {\n stars: 2,\n count: ratingDistribution.twoStars,\n percentage: totalRatings > 0 ? (ratingDistribution.twoStars / totalRatings) * 100 : 0,\n },\n {\n stars: 1,\n count: ratingDistribution.oneStar,\n percentage: totalRatings > 0 ? (ratingDistribution.oneStar / totalRatings) * 100 : 0,\n },\n ]\n : [];\n\n // Parse permissions from manifest or requiredPermissions\n const permissions = safeProduct.requiredPermissions || [];\n\n // Parse integrations from integrationDeps\n const integrations = safeProduct.integrationDeps || [];\n\n // Pricing display\n const getPriceDisplay = () => {\n if (safeProduct.pricingModel === 'FREE') return 'Free';\n if (safeProduct.pricingModel === 'SUBSCRIPTION') {\n return `${formatPrice(safeProduct.price, safeProduct.currency)}/mo`;\n }\n return formatPrice(safeProduct.price, safeProduct.currency);\n };\n\n // Check if product is free\n const isFree = safeProduct.pricingModel === 'FREE' || safeProduct.price === 0;\n\n return (\n <div className=\"h-full overflow-y-auto bg-bg-base\">\n {/* Hero Section - Two Column Layout */}\n <div className=\"relative bg-bg-surface\">\n <div className=\"mx-auto max-w-7xl\">\n <div className=\"flex flex-col lg:flex-row\">\n {/* Left Column - Product Info */}\n <div className=\"flex-1 px-6 py-8 lg:max-w-xl lg:py-12\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"mb-6 cursor-pointer inline-flex items-center gap-2 text-sm text-text-muted transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n\n {/* App Title - Large Typography */}\n <h1 className=\"mb-4 text-4xl font-normal leading-tight text-text-primary lg:text-5xl\">\n {safeProduct.name}\n </h1>\n\n {/* Publisher */}\n <div className=\"mb-2\">\n {publisher?.websiteUrl ? (\n <a\n href={publisher.websiteUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-base font-medium text-text-link hover:underline\"\n >\n {publisher.name}\n </a>\n ) : (\n <span className=\"text-base font-medium text-text-primary\">\n {publisher?.name || safeProduct.authorName || 'Unknown Publisher'}\n </span>\n )}\n {publisher?.isVerified && (\n <ShieldCheck className=\"ml-1.5 inline size-4 text-status-success-text\" />\n )}\n </div>\n\n {/* Metadata */}\n <p className=\"mb-6 text-sm text-text-muted\">\n {safeProduct.category && <span>{safeProduct.category}</span>}\n {safeProduct.category && ' · '}\n {formatNumber(safeProduct.downloads)}+ downloads\n </p>\n\n {/* Stats Row */}\n <div className=\"mb-6 flex items-center gap-4\">\n {/* App Icon Badge */}\n <div className=\"flex size-12 items-center justify-center overflow-hidden rounded-xl bg-bg-sunken\">\n {safeProduct.icon ? (\n <img src={safeProduct.icon} alt=\"\" className=\"size-full object-cover\" />\n ) : (\n <span className=\"text-lg font-bold text-text-muted\">\n {safeProduct.name.charAt(0)}\n </span>\n )}\n </div>\n\n {/* Rating */}\n <div className=\"flex items-center gap-1 border-l border-border-default pl-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {(safeProduct.rating || 0).toFixed(1)}\n </span>\n <Star className=\"size-4 fill-current text-status-warning-text\" />\n <span className=\"ml-1 text-xs text-text-muted\">\n {formatNumber(reviewsCount || safeProduct.reviewCount)} reviews\n </span>\n </div>\n\n {/* Downloads */}\n <div className=\"border-l border-border-default pl-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {formatNumber(safeProduct.downloads)}+\n </span>\n <p className=\"text-xs text-text-muted\">Downloads</p>\n </div>\n\n {/* Type Badge */}\n <div className=\"border-l border-border-default pl-4\">\n <span className=\"rounded bg-bg-sunken px-2 py-1 text-xs font-medium text-text-primary\">\n {safeProduct.type}\n </span>\n <p className=\"mt-0.5 text-xs text-text-muted\">Type</p>\n </div>\n </div>\n\n {/* Action Buttons Row */}\n {actionError && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-bg bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {actionError}\n </div>\n )}\n\n <div className=\"mb-6 flex items-center gap-3\">\n {isInstalledInCurrentWorkspace ? (\n <button\n type=\"button\"\n disabled\n className=\"flex items-center gap-2 rounded-full bg-status-success-bg-subtle px-8 py-3 font-medium text-status-success-text cursor-default\"\n >\n <svg\n className=\"size-4\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"2,8 6,12 14,4\" />\n </svg>\n Installed\n </button>\n ) : isPurchased && !isFree ? (\n <button\n type=\"button\"\n onClick={handleInstallFree}\n className=\"cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Install to Workspace\n </button>\n ) : isFree ? (\n <button\n type=\"button\"\n onClick={handleInstallFree}\n className=\"cursor-pointer rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90\"\n >\n Install\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleBuyNow}\n disabled={buyingNow || checkingOut}\n className=\"cursor-pointer flex items-center gap-2 rounded-full bg-action-primary-bg px-8 py-3 font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-70\"\n >\n {buyingNow || checkingOut ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n `Buy ${getPriceDisplay()}`\n )}\n </button>\n )}\n\n {/* Add to Cart / Wishlist */}\n {!isFree && !isPurchased && (\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n className=\"cursor-pointer flex items-center gap-2 rounded-full border border-border-default px-4 py-3 text-sm text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n {addingToCart ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : isInCart ? (\n <Check className=\"size-4 text-status-success-text\" />\n ) : (\n <ShoppingCart className=\"size-4\" />\n )}\n {isInCart ? 'In Cart' : 'Add to Cart'}\n </button>\n )}\n </div>\n\n {/* Info Text */}\n <p className=\"flex items-center gap-2 text-xs text-text-muted\">\n <Package className=\"size-4\" />\n This app is available for your workspace\n </p>\n\n {/* License Status */}\n {userEntitlement && (\n <p className=\"mt-2 flex items-center gap-2 text-xs text-text-muted\">\n <ShieldCheck className=\"size-4 text-status-success-text\" />\n License: {userEntitlement.status}\n {userEntitlement.expiresAt &&\n ` (Expires: ${new Date(userEntitlement.expiresAt).toLocaleDateString()})`}\n </p>\n )}\n </div>\n\n {/* Right Column - Hero Screenshot */}\n <div className=\"relative flex-1 bg-bg-sunken lg:min-h-[500px]\">\n {safeProduct.screenshots && safeProduct.screenshots.length > 0 ? (\n <img\n src={safeProduct.screenshots[0]}\n alt={`${safeProduct.name} screenshot`}\n className=\"size-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full min-h-[300px] items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 flex size-24 items-center justify-center rounded-2xl bg-bg-surface text-4xl font-bold text-text-muted\">\n {safeProduct.name.charAt(0)}\n </div>\n <p className=\"text-sm text-text-muted\">No preview available</p>\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* Main Content Area */}\n <main className=\"mx-auto max-w-7xl px-6 py-8\">\n <div className=\"flex flex-col gap-8 lg:flex-row\">\n {/* Left Content */}\n <div className=\"flex-1 lg:max-w-3xl\">\n {/* Screenshots Gallery */}\n {safeProduct.screenshots && safeProduct.screenshots.length > 1 && (\n <section className=\"mb-8\">\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {safeProduct.screenshots.map((screenshot, index) => (\n <div\n key={index}\n className=\"relative h-64 w-96 flex-shrink-0 overflow-hidden rounded-xl bg-bg-sunken\"\n >\n <img\n src={screenshot}\n alt={`Screenshot ${index + 1}`}\n className=\"size-full object-cover\"\n />\n </div>\n ))}\n </div>\n </section>\n )}\n\n {/* About this app */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <h2 className=\"text-lg font-medium text-text-primary\">About this app</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-text-secondary\">\n <p className={!showFullDescription ? 'line-clamp-4' : ''}>\n {safeProduct.longDescription ||\n safeProduct.description ||\n 'No description available.'}\n </p>\n {((safeProduct.longDescription || safeProduct.description)?.length ?? 0) > 200 && (\n <button\n type=\"button\"\n onClick={() => setShowFullDescription(!showFullDescription)}\n className=\"cursor-pointer mt-3 text-sm font-medium text-text-link\"\n >\n {showFullDescription ? 'Show less' : 'Show more'}\n </button>\n )}\n </div>\n {/* Tags */}\n {safeProduct.tags && safeProduct.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-2\">\n {safeProduct.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full border border-border-default px-3 py-1.5 text-xs text-text-muted transition-colors hover:bg-bg-sunken\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </section>\n\n {/* Ratings & Reviews */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <h2 className=\"text-lg font-medium text-text-primary\">Ratings & Reviews</h2>\n </div>\n\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-5\">\n {reviews && reviews.length > 0 ? (\n <div className=\"flex items-start gap-8\">\n {/* Rating Score */}\n <div className=\"text-center\">\n <p className=\"text-5xl font-light text-text-primary\">\n {(safeProduct.rating || 0).toFixed(1)}\n </p>\n <div className=\"my-2 flex justify-center gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star\n key={star}\n className={`size-4 ${\n star <= Math.round(safeProduct.rating || 0)\n ? 'fill-current text-status-warning-text'\n : 'text-text-muted'\n }`}\n />\n ))}\n </div>\n <p className=\"text-sm text-text-muted\">\n {formatNumber(reviewsCount || safeProduct.reviewCount)} reviews\n </p>\n </div>\n\n {/* Rating Distribution */}\n {ratingDistributionData.length > 0 && (\n <div className=\"flex-1 space-y-1.5\">\n {ratingDistributionData.map((item) => (\n <RatingBar key={item.stars} {...item} />\n ))}\n </div>\n )}\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"mb-3 flex gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => (\n <Star key={star} className=\"size-5 text-border-default\" />\n ))}\n </div>\n <p className=\"font-medium text-text-primary\">No reviews yet</p>\n <p className=\"mt-1 text-sm text-text-muted\">\n Be the first to share your experience with this app.\n </p>\n </div>\n )}\n\n {/* Write Review */}\n <div className=\"mt-6 border-t border-border-default pt-4\">\n {reviewNotice && (\n <p\n role={reviewNoticeTone === 'success' ? 'status' : 'alert'}\n className={`mb-3 rounded-lg px-3 py-2 text-sm ${\n reviewNoticeTone === 'success'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-error-bg-subtle text-status-error-text'\n }`}\n >\n {reviewNotice}\n </p>\n )}\n {isPurchased || productDetails?.userReview ? (\n <div className=\"space-y-4\">\n <button\n type=\"button\"\n onClick={() =>\n isReviewFormOpen ? setIsReviewFormOpen(false) : openReviewForm()\n }\n className=\"w-full cursor-pointer py-2 text-center text-sm font-medium text-text-primary transition-colors hover:text-text-link\"\n >\n {productDetails?.userReview\n ? isReviewFormOpen\n ? 'Cancel review editing'\n : 'Edit your review'\n : isReviewFormOpen\n ? 'Cancel review'\n : 'Write a Review'}\n </button>\n\n {isReviewFormOpen && (\n <div className=\"rounded-xl border border-border-default bg-bg-sunken p-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Your rating</p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => setReviewRating(star)}\n className={`cursor-pointer rounded-full border px-3 py-1.5 text-sm transition-colors ${\n reviewRating === star\n ? 'border-action-primary-border bg-action-primary-bg text-action-primary-text'\n : 'border-border-default bg-bg-surface text-text-primary hover:bg-bg-surface'\n }`}\n >\n {star} ★\n </button>\n ))}\n </div>\n </div>\n\n <label className=\"mt-4 block text-sm font-medium text-text-primary\">\n Title\n <input\n aria-label=\"Review title\"\n value={reviewTitle}\n onChange={(event) => setReviewTitle(event.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-border\"\n placeholder=\"Summarize your experience\"\n />\n </label>\n\n <label className=\"mt-4 block text-sm font-medium text-text-primary\">\n Review\n <textarea\n aria-label=\"Review body\"\n value={reviewComment}\n onChange={(event) => setReviewComment(event.target.value)}\n rows={4}\n required\n aria-required=\"true\"\n className=\"mt-2 w-full rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-border\"\n placeholder=\"What worked well? What should improve?\"\n />\n </label>\n\n <div className=\"mt-4 flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={handleSubmitReview}\n disabled={creatingReview || updatingReview}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {creatingReview || updatingReview\n ? 'Saving...'\n : productDetails?.userReview\n ? 'Update Review'\n : 'Submit Review'}\n </button>\n {productDetails?.userReview && (\n <button\n type=\"button\"\n onClick={handleDeleteReview}\n disabled={deletingReview}\n className=\"cursor-pointer rounded-lg border border-status-error-border px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {deletingReview ? 'Removing...' : 'Delete Review'}\n </button>\n )}\n </div>\n </div>\n )}\n </div>\n ) : (\n <p className=\"text-center text-sm text-text-muted\">\n Purchase this app to submit a review.\n </p>\n )}\n </div>\n </div>\n\n {/* Reviews List */}\n {reviews && reviews.length > 0 && (\n <div className=\"mt-4 space-y-3\">\n <ul className=\"space-y-3\">\n {(showAllReviews ? reviews : reviews.slice(0, 2)).map((review) => (\n <li key={review.id}>\n <ReviewCard\n review={review}\n onMarkHelpful={handleMarkHelpful}\n markingHelpful={markingHelpful && helpfulReviewId === review.id}\n />\n </li>\n ))}\n </ul>\n {reviews.length > 2 && (\n <button\n type=\"button\"\n onClick={() => setShowAllReviews(!showAllReviews)}\n className=\"cursor-pointer text-sm font-medium text-text-link\"\n >\n {showAllReviews ? 'Show less' : `Show all ${reviews.length} reviews`}\n </button>\n )}\n </div>\n )}\n {(!reviews || reviews.length === 0) && (\n <p className=\"mt-4 text-sm text-text-muted\">\n No reviews yet. Be the first to review!\n </p>\n )}\n </section>\n\n {/* Permissions */}\n {permissions.length > 0 && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Required Permissions</h2>\n <div className=\"space-y-2\">\n {permissions.map((permission) => (\n <div\n key={permission}\n className=\"flex items-center gap-3 rounded-lg bg-bg-surface px-4 py-3\"\n >\n <ShieldCheck className=\"size-5 text-status-success-text\" />\n <span className=\"text-sm text-text-primary\">{permission}</span>\n </div>\n ))}\n </div>\n </section>\n )}\n\n {/* Integrations */}\n {integrations.length > 0 && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Integrations</h2>\n <div className=\"flex flex-wrap gap-2\">\n {integrations.map((integration) => (\n <span\n key={integration}\n className=\"rounded-lg bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary\"\n >\n {integration}\n </span>\n ))}\n </div>\n </section>\n )}\n\n {/* Additional Information */}\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-medium text-text-primary\">Additional Information</h2>\n <div className=\"grid grid-cols-2 gap-6 md:grid-cols-3\">\n {safeProduct.manifestVersion && (\n <div>\n <p className=\"text-xs text-text-muted\">Version</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.manifestVersion}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-xs text-text-muted\">Type</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">{safeProduct.type}</p>\n </div>\n {safeProduct.publishedAt && (\n <div>\n <p className=\"text-xs text-text-muted\">Published</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {new Date(safeProduct.publishedAt).toLocaleDateString()}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-xs text-text-muted\">Updated</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {new Date(safeProduct.updatedAt).toLocaleDateString()}\n </p>\n </div>\n {safeProduct.license && (\n <div>\n <p className=\"text-xs text-text-muted\">License</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.license}\n </p>\n </div>\n )}\n {safeProduct.minPlatformVersion && (\n <div>\n <p className=\"text-xs text-text-muted\">Min Platform Version</p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {safeProduct.minPlatformVersion}\n </p>\n </div>\n )}\n </div>\n </section>\n </div>\n\n {/* Right Sidebar */}\n <div className=\"w-full lg:w-80\">\n {/* App Support */}\n {(safeProduct.documentationUrl || safeProduct.repositoryUrl || publisher?.email) && (\n <div className=\"mb-6 rounded-xl border border-border-default bg-bg-surface p-4\">\n <h3 className=\"font-medium text-text-primary\">App support</h3>\n <div className=\"mt-3 space-y-2 text-sm\">\n {safeProduct.documentationUrl && (\n <a\n href={safeProduct.documentationUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"block text-text-link hover:underline\"\n >\n Documentation\n </a>\n )}\n {safeProduct.repositoryUrl && (\n <a\n href={safeProduct.repositoryUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"block text-text-link hover:underline\"\n >\n Source repository\n </a>\n )}\n {publisher?.email && (\n <a\n href={`mailto:${publisher.email}`}\n className=\"block text-text-link hover:underline\"\n >\n Contact publisher\n </a>\n )}\n </div>\n </div>\n )}\n\n {/* More by Publisher */}\n {publisher && (\n <div className=\"mb-6 rounded-xl border border-border-default bg-bg-surface p-4\">\n <h3 className=\"font-medium text-text-primary\">Publisher</h3>\n <p className=\"mt-2 text-sm text-text-primary\">{publisher.name}</p>\n {publisher.bio && <p className=\"mt-2 text-sm text-text-muted\">{publisher.bio}</p>}\n {publisher.websiteUrl && (\n <a\n href={publisher.websiteUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"mt-3 inline-block text-sm text-text-link hover:underline\"\n >\n Visit publisher website\n </a>\n )}\n </div>\n )}\n\n {/* Similar Apps */}\n {relatedProducts && relatedProducts.length > 0 && (\n <div>\n <div className=\"mb-4 flex items-center justify-between text-left\">\n <span className=\"font-medium text-text-primary\">Similar apps</span>\n </div>\n <div className=\"space-y-3\">\n {relatedProducts.slice(0, 4).map((related) => (\n <button\n type=\"button\"\n key={related.id}\n onClick={() => navigate(`${basePath}/marketplace/product/${related.id}`)}\n className=\"flex w-full cursor-pointer items-center gap-3 rounded-lg p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n {related.icon ? (\n <img\n src={related.icon}\n alt={related.name}\n className=\"size-12 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex size-12 items-center justify-center rounded-xl bg-bg-sunken text-lg font-bold text-text-muted\">\n {related.name.charAt(0)}\n </div>\n )}\n <div className=\"flex-1 overflow-hidden\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {related.name}\n </p>\n <p className=\"text-xs text-text-muted\">{publisher?.name || 'Unknown'}</p>\n {(related.reviewCount ?? 0) > 0 && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-text-muted\">\n {(related.rating || 0).toFixed(1)}\n </span>\n <Star className=\"size-3 fill-current text-status-warning-text\" />\n </div>\n )}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n </main>\n\n {/* Install App Modal */}\n {safeProduct && (\n <InstallAppModal\n isOpen={isInstallModalOpen}\n onClose={() => setIsInstallModalOpen(false)}\n app={{\n id: safeProduct.id,\n name: safeProduct.name,\n slug: safeProduct.slug,\n icon: safeProduct.icon,\n type: safeProduct.type,\n version: safeProduct.manifestVersion,\n }}\n purchaseState={isPurchased && !isFree ? 'purchased' : 'free'}\n onInstallSuccess={handleInstallSuccess}\n onInstallError={handleInstallError}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|