@burdenoff/microfe-devportal 2026.805.3 → 2026.828.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/components/GrantDelegationModal.d.ts.map +1 -1
- package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
- package/dist/devportal/hooks/useGraphQL.d.ts +0 -12
- package/dist/devportal/hooks/useGraphQL.d.ts.map +1 -1
- package/dist/devportal/hooks/useGraphQL.js.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.d.ts.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.d.ts.map +1 -1
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/types/index.d.ts.map +1 -1
- package/dist/devportal/types/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrantDelegationModal.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,EAAE,EACR,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"GrantDelegationModal.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,EAAE,EACR,MAAM,OAAO,CAAC;AAyCf,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,yBAAyB,CAilB9D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: workspaceClient ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation({\n refetchQueries: [\"CurrentUserDelegations\"],\n awaitRefetchQueries: true,\n });\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n if (result.error) {\n setError(\n result.error.message ??\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const delegation = result.data?.grantDelegation;\n if (!delegation) {\n setError(\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) 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 delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-bg-surface shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p id={descriptionId} className=\"mt-1 text-sm text-text-secondary\">\n Allow{\" \"}\n <span className=\"font-medium text-text-primary\">{appName}</span>{\" \"}\n to act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-text-secondary hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-text-secondary\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-text-secondary\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport type { ApolloClient } from \"@apollo/client/core\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: (workspaceClient as unknown as ApolloClient | null) ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation({\n refetchQueries: [\"CurrentUserDelegations\"],\n awaitRefetchQueries: true,\n });\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n if (result.error) {\n setError(\n result.error.message ??\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const delegation = result.data?.grantDelegation;\n if (!delegation) {\n setError(\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) 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 delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-bg-surface shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p id={descriptionId} className=\"mt-1 text-sm text-text-secondary\">\n Allow{\" \"}\n <span className=\"font-medium text-text-primary\">{appName}</span>{\" \"}\n to act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-text-secondary hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-text-secondary\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-text-secondary\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-bg-sunken\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { ApolloClient } from '@apollo/client/core';
|
|
2
2
|
|
|
3
3
|
export * from '../../generated/global-operations';
|
|
4
|
-
declare global {
|
|
5
|
-
interface ImportMeta {
|
|
6
|
-
readonly env: {
|
|
7
|
-
readonly VITE_GRAPHQL_ENDPOINT?: string;
|
|
8
|
-
readonly VITE_AUTH_GRAPHQL_ENDPOINT?: string;
|
|
9
|
-
readonly VITE_DEV_ACTOR_ID?: string;
|
|
10
|
-
readonly VITE_DEV_ACTOR_TYPE?: string;
|
|
11
|
-
readonly DEV?: boolean;
|
|
12
|
-
[key: string]: string | boolean | undefined;
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
4
|
export declare const apolloClient: ApolloClient;
|
|
17
5
|
/**
|
|
18
6
|
* Error handling utilities
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGraphQL.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/useGraphQL.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EAKb,MAAM,qBAAqB,CAAC;AA0C7B,cAAc,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"useGraphQL.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/useGraphQL.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EAKb,MAAM,qBAAqB,CAAC;AA0C7B,cAAc,mCAAmC,CAAC;AA2JlD,eAAO,MAAM,YAAY,cAkKvB,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CACrC;AAGD,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,aAAa,CAAC,EAAE,SAAS;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;KACrC,EAAE,CAAC;IACJ,YAAY,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,EAAE,CAgC5E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAG9D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAK3D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAKjE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGraphQL.js","names":[],"sources":["../../../src/devportal/hooks/useGraphQL.ts"],"sourcesContent":["/**\n * Apollo Client hooks and utilities for DevPortal\n *\n * This file provides the GraphQL client setup and custom hooks for the Developer Portal.\n * It wraps the generated Apollo hooks with additional functionality like error handling,\n * loading states, and authentication context.\n */\n\nimport {\n ApolloClient,\n InMemoryCache,\n createHttpLink,\n from,\n split,\n} from \"@apollo/client/core\";\nimport { CombinedGraphQLErrors, ServerError } from \"@apollo/client/errors\";\nimport { setContext } from \"@apollo/client/link/context\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { createClient as createWsClient } from \"graphql-ws\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n// Re-export all generated hooks for direct use\nexport * from \"../../generated/global-operations\";\n\n// Vite environment variable declaration\ndeclare global {\n interface ImportMeta {\n readonly env: {\n readonly VITE_GRAPHQL_ENDPOINT?: string;\n readonly VITE_AUTH_GRAPHQL_ENDPOINT?: string;\n readonly VITE_DEV_ACTOR_ID?: string;\n readonly VITE_DEV_ACTOR_TYPE?: string;\n readonly DEV?: boolean;\n [key: string]: string | boolean | undefined;\n };\n }\n}\n\n// Development mode check\nconst isDevelopment =\n typeof import.meta !== \"undefined\" && import.meta.env?.DEV === true;\n\n// Default GraphQL endpoints - can be overridden via environment.\n// In standalone dev mode we route through same-origin Vite proxies to avoid CORS.\nconst DEFAULT_GRAPHQL_ENDPOINT =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_GRAPHQL_ENDPOINT) ||\n (isDevelopment ? \"/devportal-graphql\" : \"http://localhost:4015/graphql\");\n\nconst DEFAULT_AUTH_GRAPHQL_ENDPOINT =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_AUTH_GRAPHQL_ENDPOINT) ||\n (isDevelopment ? \"/auth-graphql\" : \"http://localhost:4011/graphql\");\n\nconst AUTH_OPERATION_NAMES = new Set([\n \"CurrentUserApps\",\n \"GetAuthApp\",\n \"AppsActingAsCurrentUser\",\n \"CreateAuthApp\",\n \"UpdateAuthApp\",\n \"DeactivateApp\",\n \"RotateAppSecret\",\n \"AuthenticateApp\",\n \"GenerateAppToken\",\n \"GenerateAppAsUserToken\",\n \"ExchangeToken\",\n \"CurrentUserDelegations\",\n \"CurrentUserGrantedDelegations\",\n \"GrantDelegation\",\n \"RevokeDelegation\",\n \"MyApiKeys\",\n \"GenerateAPIKey\",\n \"RevokeAPIKeyAuth\",\n \"RotateAPIKeyAuth\",\n \"AuthenticateAPIKey\",\n]);\n\n// Development actor ID - simulates gateway-injected headers\n// In production, the gateway adds these after JWT verification\nconst DEV_ACTOR_ID =\n (typeof import.meta !== \"undefined\" && import.meta.env?.VITE_DEV_ACTOR_ID) ||\n \"dev-user-001\";\n\nconst DEV_ACTOR_TYPE =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_DEV_ACTOR_TYPE) ||\n \"user\";\n\nfunction toWsUrl(httpUrl: string): string {\n return httpUrl.replace(/^https/, \"wss\").replace(/^http/, \"ws\");\n}\n\n// Create the HTTP links\nconst devportalHttpLink = createHttpLink({\n uri: DEFAULT_GRAPHQL_ENDPOINT,\n});\n\nconst authHttpLink = createHttpLink({\n uri: DEFAULT_AUTH_GRAPHQL_ENDPOINT,\n});\n\nconst httpLink = split(\n ({ operationName }) => {\n if (!operationName) {\n return false;\n }\n\n return AUTH_OPERATION_NAMES.has(operationName);\n },\n authHttpLink,\n devportalHttpLink,\n);\n\n// Auth link to add authentication headers\nconst authLink = setContext((_, { headers }) => {\n // Get auth token from storage or context\n const token =\n typeof window !== \"undefined\" ? localStorage.getItem(\"auth_token\") : null;\n\n // Build headers\n const authHeaders: Record<string, string> = {\n ...headers,\n };\n\n if (token) {\n authHeaders[\"authorization\"] = `Bearer ${token}`;\n }\n\n // In development mode, add actor headers to simulate gateway behavior\n // In production, the API Gateway adds these headers after JWT verification\n if (isDevelopment) {\n // Use actor ID from localStorage (set by dev tools) or fall back to env/default\n const actorId =\n (typeof window !== \"undefined\" && localStorage.getItem(\"dev_actor_id\")) ||\n DEV_ACTOR_ID;\n const actorType =\n (typeof window !== \"undefined\" &&\n localStorage.getItem(\"dev_actor_type\")) ||\n DEV_ACTOR_TYPE;\n\n authHeaders[\"x-actor-id\"] = actorId;\n authHeaders[\"x-actor-type\"] = actorType;\n }\n\n return { headers: authHeaders };\n});\n\nconst errorLink = onError(({ error }) => {\n if (!error) return;\n\n const graphQLErrors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n\n if (hasSessionAuthErrors(graphQLErrors)) {\n redirectToLogin();\n return;\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n});\n\nconst wsLink = new GraphQLWsLink(\n createWsClient({\n url: toWsUrl(DEFAULT_GRAPHQL_ENDPOINT),\n connectionParams: () => {\n // Grab the auth token from the auth link's context builder for WS handshake\n return { authorization: \"\" }; // token injected server-side from cookie/session\n },\n }),\n);\n\nconst splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, authLink, httpLink]),\n);\n\n// Create the Apollo Client instance\n// NOTE: defaultOptions.errorPolicy is intentionally omitted. Apollo Client v4\n// guards it behind a DeclareDefaultOptions module augmentation that does not\n// resolve reliably in CI's frozen install, so it is left at the v4 default\n// (\"none\"). Per-operation errorPolicy: \"all\" can be set on individual hooks\n// where partial-data-on-error handling is required.\nexport const apolloClient = new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache({\n typePolicies: {\n Query: {\n fields: {\n // Pagination policies for connection types\n developerOrganizations: {\n keyArgs: [\"tier\", \"isVerified\", \"isCertified\", \"partnerTier\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n applications: {\n keyArgs: [\"status\", \"visibility\", \"developerId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgSales: {\n keyArgs: [\n \"organizationId\",\n \"applicationId\",\n \"status\",\n \"pricingModel\",\n \"isPaidOut\",\n ],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgPayouts: {\n keyArgs: [\"organizationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgMembers: {\n keyArgs: [\"organizationId\", \"role\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgInvitations: {\n keyArgs: [\"organizationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n webhooks: {\n keyArgs: [\"applicationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n webhookDeliveries: {\n keyArgs: [\"webhookId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n auditLogs: {\n keyArgs: [\"resource\", \"resourceId\", \"actorId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n applicationVersions: {\n // TODO(phase1-backend): Switch to ApplicationVersionConnection cursor pagination\n // once backend ships connection type. See stability tracker.\n keyArgs: [\"applicationId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n if (Array.isArray(incoming) && Array.isArray(existing)) {\n return [...existing, ...incoming];\n }\n return incoming;\n },\n },\n },\n },\n WebhookDelivery: {\n keyFields: [\"id\"],\n },\n ApplicationVersion: {\n keyFields: [\"id\"],\n },\n DeveloperOrganization: {\n keyFields: [\"id\"],\n },\n Application: {\n keyFields: [\"id\"],\n },\n DeveloperOrgMember: {\n keyFields: [\"id\"],\n },\n DeveloperOrgInvitation: {\n keyFields: [\"id\"],\n },\n Webhook: {\n keyFields: [\"id\"],\n },\n ApiKey: {\n keyFields: [\"id\"],\n },\n OAuthClient: {\n keyFields: [\"id\"],\n },\n },\n }),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n query: {\n fetchPolicy: \"cache-first\",\n },\n },\n});\n\n/**\n * Error handling utilities\n */\nexport interface GraphQLErrorInfo {\n message: string;\n code?: string;\n path?: readonly (string | number)[];\n}\n\n// Type for Apollo errors (can be CombinedGraphQLErrors, ServerError, or Error)\nexport type ApolloErrorLike = Error & {\n graphQLErrors?: readonly {\n message: string;\n extensions?: { code?: string };\n path?: readonly (string | number)[];\n }[];\n networkError?: Error | null;\n};\n\nexport function parseGraphQLError(error: ApolloErrorLike): GraphQLErrorInfo[] {\n if (error instanceof CombinedGraphQLErrors) {\n return error.errors.map((err) => ({\n message: err.message,\n code: err.extensions?.code as string | undefined,\n path: err.path,\n }));\n }\n\n if (error.graphQLErrors && error.graphQLErrors.length > 0) {\n return error.graphQLErrors.map((err) => ({\n message: err.message,\n code: err.extensions?.code as string | undefined,\n path: err.path,\n }));\n }\n\n if (error instanceof ServerError || error.networkError) {\n return [\n {\n message: \"Network error. Please check your connection and try again.\",\n code: \"NETWORK_ERROR\",\n },\n ];\n }\n\n return [\n {\n message: error.message || \"An unexpected error occurred\",\n code: \"UNKNOWN_ERROR\",\n },\n ];\n}\n\n/**\n * Get a user-friendly error message from an Apollo error\n */\nexport function getErrorMessage(error: ApolloErrorLike): string {\n const errors = parseGraphQLError(error);\n return errors.map((e) => e.message).join(\", \");\n}\n\n/**\n * Check if an error is an authentication error\n */\nexport function isAuthError(error: ApolloErrorLike): boolean {\n const errors = parseGraphQLError(error);\n return errors.some(\n (e) => e.code === \"UNAUTHENTICATED\" || e.code === \"FORBIDDEN\",\n );\n}\n\n/**\n * Check if an error is a validation error\n */\nexport function isValidationError(error: ApolloErrorLike): boolean {\n const errors = parseGraphQLError(error);\n return errors.some(\n (e) => e.code === \"BAD_USER_INPUT\" || e.code === \"VALIDATION_ERROR\",\n );\n}\n\n/**\n * Clear Apollo cache (useful for logout)\n */\nexport async function clearApolloCache(): Promise<void> {\n await apolloClient.clearStore();\n}\n\n/**\n * Reset Apollo store and refetch queries\n */\nexport async function resetApolloStore(): Promise<void> {\n await apolloClient.resetStore();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useGraphQL.js","names":[],"sources":["../../../src/devportal/hooks/useGraphQL.ts"],"sourcesContent":["/**\n * Apollo Client hooks and utilities for DevPortal\n *\n * This file provides the GraphQL client setup and custom hooks for the Developer Portal.\n * It wraps the generated Apollo hooks with additional functionality like error handling,\n * loading states, and authentication context.\n */\n\nimport {\n ApolloClient,\n InMemoryCache,\n createHttpLink,\n from,\n split,\n} from \"@apollo/client/core\";\nimport { CombinedGraphQLErrors, ServerError } from \"@apollo/client/errors\";\nimport { setContext } from \"@apollo/client/link/context\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { createClient as createWsClient } from \"graphql-ws\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n// Re-export all generated hooks for direct use\nexport * from \"../../generated/global-operations\";\n\n// Development mode check\nconst isDevelopment =\n typeof import.meta !== \"undefined\" && import.meta.env?.DEV === true;\n\n// Default GraphQL endpoints - can be overridden via environment.\n// In standalone dev mode we route through same-origin Vite proxies to avoid CORS.\nconst DEFAULT_GRAPHQL_ENDPOINT =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_GRAPHQL_ENDPOINT) ||\n (isDevelopment ? \"/devportal-graphql\" : \"http://localhost:4015/graphql\");\n\nconst DEFAULT_AUTH_GRAPHQL_ENDPOINT =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_AUTH_GRAPHQL_ENDPOINT) ||\n (isDevelopment ? \"/auth-graphql\" : \"http://localhost:4011/graphql\");\n\nconst AUTH_OPERATION_NAMES = new Set([\n \"CurrentUserApps\",\n \"GetAuthApp\",\n \"AppsActingAsCurrentUser\",\n \"CreateAuthApp\",\n \"UpdateAuthApp\",\n \"DeactivateApp\",\n \"RotateAppSecret\",\n \"AuthenticateApp\",\n \"GenerateAppToken\",\n \"GenerateAppAsUserToken\",\n \"ExchangeToken\",\n \"CurrentUserDelegations\",\n \"CurrentUserGrantedDelegations\",\n \"GrantDelegation\",\n \"RevokeDelegation\",\n \"MyApiKeys\",\n \"GenerateAPIKey\",\n \"RevokeAPIKeyAuth\",\n \"RotateAPIKeyAuth\",\n \"AuthenticateAPIKey\",\n]);\n\n// Development actor ID - simulates gateway-injected headers\n// In production, the gateway adds these after JWT verification\nconst DEV_ACTOR_ID =\n (typeof import.meta !== \"undefined\" && import.meta.env?.VITE_DEV_ACTOR_ID) ||\n \"dev-user-001\";\n\nconst DEV_ACTOR_TYPE =\n (typeof import.meta !== \"undefined\" &&\n import.meta.env?.VITE_DEV_ACTOR_TYPE) ||\n \"user\";\n\nfunction toWsUrl(httpUrl: string): string {\n return httpUrl.replace(/^https/, \"wss\").replace(/^http/, \"ws\");\n}\n\n// Create the HTTP links\nconst devportalHttpLink = createHttpLink({\n uri: DEFAULT_GRAPHQL_ENDPOINT,\n});\n\nconst authHttpLink = createHttpLink({\n uri: DEFAULT_AUTH_GRAPHQL_ENDPOINT,\n});\n\nconst httpLink = split(\n ({ operationName }) => {\n if (!operationName) {\n return false;\n }\n\n return AUTH_OPERATION_NAMES.has(operationName);\n },\n authHttpLink,\n devportalHttpLink,\n);\n\n// Auth link to add authentication headers\nconst authLink = setContext((_, { headers }) => {\n // Get auth token from storage or context\n const token =\n typeof window !== \"undefined\" ? localStorage.getItem(\"auth_token\") : null;\n\n // Build headers\n const authHeaders: Record<string, string> = {\n ...headers,\n };\n\n if (token) {\n authHeaders[\"authorization\"] = `Bearer ${token}`;\n }\n\n // In development mode, add actor headers to simulate gateway behavior\n // In production, the API Gateway adds these headers after JWT verification\n if (isDevelopment) {\n // Use actor ID from localStorage (set by dev tools) or fall back to env/default\n const actorId =\n (typeof window !== \"undefined\" && localStorage.getItem(\"dev_actor_id\")) ||\n DEV_ACTOR_ID;\n const actorType =\n (typeof window !== \"undefined\" &&\n localStorage.getItem(\"dev_actor_type\")) ||\n DEV_ACTOR_TYPE;\n\n authHeaders[\"x-actor-id\"] = actorId;\n authHeaders[\"x-actor-type\"] = actorType;\n }\n\n return { headers: authHeaders };\n});\n\nconst errorLink = onError(({ error }) => {\n if (!error) return;\n\n const graphQLErrors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n\n if (hasSessionAuthErrors(graphQLErrors)) {\n redirectToLogin();\n return;\n }\n\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n});\n\nconst wsLink = new GraphQLWsLink(\n createWsClient({\n url: toWsUrl(DEFAULT_GRAPHQL_ENDPOINT),\n connectionParams: () => {\n // Grab the auth token from the auth link's context builder for WS handshake\n return { authorization: \"\" }; // token injected server-side from cookie/session\n },\n }),\n);\n\nconst splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, authLink, httpLink]),\n);\n\n// Create the Apollo Client instance\n// NOTE: defaultOptions.errorPolicy is intentionally omitted. Apollo Client v4\n// guards it behind a DeclareDefaultOptions module augmentation that does not\n// resolve reliably in CI's frozen install, so it is left at the v4 default\n// (\"none\"). Per-operation errorPolicy: \"all\" can be set on individual hooks\n// where partial-data-on-error handling is required.\nexport const apolloClient = new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache({\n typePolicies: {\n Query: {\n fields: {\n // Pagination policies for connection types\n developerOrganizations: {\n keyArgs: [\"tier\", \"isVerified\", \"isCertified\", \"partnerTier\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n applications: {\n keyArgs: [\"status\", \"visibility\", \"developerId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgSales: {\n keyArgs: [\n \"organizationId\",\n \"applicationId\",\n \"status\",\n \"pricingModel\",\n \"isPaidOut\",\n ],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgPayouts: {\n keyArgs: [\"organizationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) {\n return incoming;\n }\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgMembers: {\n keyArgs: [\"organizationId\", \"role\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n developerOrgInvitations: {\n keyArgs: [\"organizationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n webhooks: {\n keyArgs: [\"applicationId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n webhookDeliveries: {\n keyArgs: [\"webhookId\", \"status\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n auditLogs: {\n keyArgs: [\"resource\", \"resourceId\", \"actorId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n return {\n ...incoming,\n edges: [...(existing?.edges || []), ...incoming.edges],\n };\n },\n },\n applicationVersions: {\n // TODO(phase1-backend): Switch to ApplicationVersionConnection cursor pagination\n // once backend ships connection type. See stability tracker.\n keyArgs: [\"applicationId\"],\n merge(existing, incoming, { args }) {\n if (!args?.offset || args.offset === 0) return incoming;\n if (Array.isArray(incoming) && Array.isArray(existing)) {\n return [...existing, ...incoming];\n }\n return incoming;\n },\n },\n },\n },\n WebhookDelivery: {\n keyFields: [\"id\"],\n },\n ApplicationVersion: {\n keyFields: [\"id\"],\n },\n DeveloperOrganization: {\n keyFields: [\"id\"],\n },\n Application: {\n keyFields: [\"id\"],\n },\n DeveloperOrgMember: {\n keyFields: [\"id\"],\n },\n DeveloperOrgInvitation: {\n keyFields: [\"id\"],\n },\n Webhook: {\n keyFields: [\"id\"],\n },\n ApiKey: {\n keyFields: [\"id\"],\n },\n OAuthClient: {\n keyFields: [\"id\"],\n },\n },\n }),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n query: {\n fetchPolicy: \"cache-first\",\n },\n },\n});\n\n/**\n * Error handling utilities\n */\nexport interface GraphQLErrorInfo {\n message: string;\n code?: string;\n path?: readonly (string | number)[];\n}\n\n// Type for Apollo errors (can be CombinedGraphQLErrors, ServerError, or Error)\nexport type ApolloErrorLike = Error & {\n graphQLErrors?: readonly {\n message: string;\n extensions?: { code?: string };\n path?: readonly (string | number)[];\n }[];\n networkError?: Error | null;\n};\n\nexport function parseGraphQLError(error: ApolloErrorLike): GraphQLErrorInfo[] {\n if (error instanceof CombinedGraphQLErrors) {\n return error.errors.map((err) => ({\n message: err.message,\n code: err.extensions?.code as string | undefined,\n path: err.path,\n }));\n }\n\n if (error.graphQLErrors && error.graphQLErrors.length > 0) {\n return error.graphQLErrors.map((err) => ({\n message: err.message,\n code: err.extensions?.code as string | undefined,\n path: err.path,\n }));\n }\n\n if (error instanceof ServerError || error.networkError) {\n return [\n {\n message: \"Network error. Please check your connection and try again.\",\n code: \"NETWORK_ERROR\",\n },\n ];\n }\n\n return [\n {\n message: error.message || \"An unexpected error occurred\",\n code: \"UNKNOWN_ERROR\",\n },\n ];\n}\n\n/**\n * Get a user-friendly error message from an Apollo error\n */\nexport function getErrorMessage(error: ApolloErrorLike): string {\n const errors = parseGraphQLError(error);\n return errors.map((e) => e.message).join(\", \");\n}\n\n/**\n * Check if an error is an authentication error\n */\nexport function isAuthError(error: ApolloErrorLike): boolean {\n const errors = parseGraphQLError(error);\n return errors.some(\n (e) => e.code === \"UNAUTHENTICATED\" || e.code === \"FORBIDDEN\",\n );\n}\n\n/**\n * Check if an error is a validation error\n */\nexport function isValidationError(error: ApolloErrorLike): boolean {\n const errors = parseGraphQLError(error);\n return errors.some(\n (e) => e.code === \"BAD_USER_INPUT\" || e.code === \"VALIDATION_ERROR\",\n );\n}\n\n/**\n * Clear Apollo cache (useful for logout)\n */\nexport async function clearApolloCache(): Promise<void> {\n await apolloClient.clearStore();\n}\n\n/**\n * Reset Apollo store and refetch queries\n */\nexport async function resetApolloStore(): Promise<void> {\n await apolloClient.resetStore();\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePackageUpload.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"usePackageUpload.d.ts","sourceRoot":"","sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AA6JD,wBAAgB,gBAAgB,CAC9B,oBAAoB,EAAE,MAAM,GAAG,SAAS,EACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,iBAAiB,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,SAAS,EAC7D,UAAU,SAAwB;eArKvB,OAAO;cACR,MAAM;WACT,MAAM,GAAG,IAAI;mBA4KL,IAAI,KAAG,OAAO,CAAC,mBAAmB,CAAC;EAoDnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePackageUpload.js","names":[],"sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"sourcesContent":["/**\n * usePackageUpload — uploads a zip package to wspace-files-svc and returns a\n * stable downloadUrl to store as ApplicationVersion.packageUrl.\n *\n * Flow (mirrors uploadBlobToFiles in @burdenoff/fe-libs/storage):\n * 1. ensureFolderByPath(\"/DevPortal/Packages\") → folderId\n * 2. initiateUpload → { sessionId, presignedUrl }\n * 3. PUT blob to presignedUrl (progress tracked via XHR)\n * 4. completeUpload → { file.downloadUrl }\n *\n * Uses raw fetch for GraphQL mutations so we can target the workspace gateway\n * without a second ApolloProvider.\n */\nimport { useCallback, useState } from \"react\";\n\nexport interface PackageUploadResult {\n fileId: string;\n downloadUrl: string;\n}\n\nexport interface PackageUploadState {\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\ntype GqlFetcher = (\n query: string,\n variables: Record<string, unknown>,\n) => Promise<Record<string, unknown>>;\n\nfunction makeFetcher(\n workspacesGatewayUrl: string,\n authToken: string | undefined,\n workspaceId: string | undefined,\n workspaceToken: string | undefined,\n): GqlFetcher {\n return async (query, variables) => {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authToken) headers[\"Authorization\"] = `Bearer ${authToken}`;\n if (workspaceToken)\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n if (workspaceId) headers[\"x-workspace-id\"] = workspaceId;\n\n const endpoint = workspacesGatewayUrl.endsWith(\"/graphql\")\n ? workspacesGatewayUrl\n : `${workspacesGatewayUrl}/workspaces/graphql`;\n\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n throw new Error(\n `GraphQL request failed: ${res.status} ${res.statusText}`,\n );\n }\n const json = (await res.json()) as {\n data?: Record<string, unknown>;\n errors?: Array<{ message: string }>;\n };\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n return json.data ?? {};\n };\n}\n\nasync function ensureFolder(gql: GqlFetcher, path: string): Promise<string> {\n const data = await gql(\n `mutation DevPortal_EnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path },\n );\n const id = (data.ensureFolderByPath as { id?: string } | undefined)?.id;\n if (!id) throw new Error(\"ensureFolderByPath returned no id\");\n return id;\n}\n\nasync function initiateUpload(\n gql: GqlFetcher,\n file: File,\n folderId: string,\n): Promise<{ sessionId: string; uploadUrl: string }> {\n const ext = file.name.includes(\".\")\n ? file.name.substring(file.name.lastIndexOf(\".\") + 1)\n : \"zip\";\n const data = await gql(\n `mutation DevPortal_InitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || \"application/zip\",\n extension: ext,\n folderId,\n visibility: \"PUBLIC\",\n },\n },\n );\n const result = data.initiateUpload as\n { uploadUrl?: string; session?: { id?: string } } | undefined;\n const sessionId = result?.session?.id;\n const uploadUrl = result?.uploadUrl;\n if (!sessionId || !uploadUrl) {\n throw new Error(\"initiateUpload did not return sessionId or uploadUrl\");\n }\n return { sessionId, uploadUrl };\n}\n\nasync function putToPresignedUrl(\n file: File,\n uploadUrl: string,\n onProgress: (pct: number) => void,\n): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.upload.addEventListener(\"progress\", (evt) => {\n if (evt.lengthComputable) {\n onProgress(10 + Math.round((evt.loaded / evt.total) * 80));\n }\n });\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`S3 PUT failed: ${xhr.status}`));\n }\n });\n xhr.addEventListener(\"error\", () =>\n reject(new Error(\"S3 PUT network error\")),\n );\n xhr.addEventListener(\"abort\", () => reject(new Error(\"S3 PUT aborted\")));\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/zip\");\n xhr.send(file);\n });\n}\n\nasync function completeUpload(\n gql: GqlFetcher,\n sessionId: string,\n folderId: string,\n fileSize: number,\n): Promise<PackageUploadResult> {\n const data = await gql(\n `mutation DevPortal_CompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n file { id downloadUrl }\n }\n }`,\n {\n input: {\n sessionId,\n folderId,\n fileSize: String(fileSize),\n },\n },\n );\n const file = (\n data.completeUpload as\n { file?: { id?: string; downloadUrl?: string } } | undefined\n )?.file;\n if (!file?.id || !file.downloadUrl) {\n throw new Error(\"completeUpload did not return file id or downloadUrl\");\n }\n return { fileId: file.id, downloadUrl: file.downloadUrl };\n}\n\nexport function usePackageUpload(\n workspacesGatewayUrl: string | undefined,\n authToken: string | undefined,\n workspaceId: string | undefined,\n getWorkspaceToken: (() => Promise<string | null>) | undefined,\n folderPath = \"/DevPortal/Packages\",\n) {\n const [state, setState] = useState<PackageUploadState>({\n uploading: false,\n progress: 0,\n error: null,\n });\n\n const upload = useCallback(\n async (file: File): Promise<PackageUploadResult> => {\n if (!workspacesGatewayUrl) {\n throw new Error(\n \"workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.\",\n );\n }\n\n const freshToken = getWorkspaceToken\n ? await getWorkspaceToken()\n : undefined;\n const gql = makeFetcher(\n workspacesGatewayUrl,\n authToken,\n workspaceId,\n freshToken ?? undefined,\n );\n\n setState({ uploading: true, progress: 0, error: null });\n try {\n const folderId = await ensureFolder(gql, folderPath);\n setState((s) => ({ ...s, progress: 5 }));\n\n const { sessionId, uploadUrl } = await initiateUpload(\n gql,\n file,\n folderId,\n );\n setState((s) => ({ ...s, progress: 10 }));\n\n await putToPresignedUrl(file, uploadUrl, (pct) =>\n setState((s) => ({ ...s, progress: pct })),\n );\n setState((s) => ({ ...s, progress: 90 }));\n\n const result = await completeUpload(\n gql,\n sessionId,\n folderId,\n file.size,\n );\n setState({ uploading: false, progress: 100, error: null });\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState({ uploading: false, progress: 0, error: message });\n throw err;\n }\n },\n [workspacesGatewayUrl, authToken, workspaceId, getWorkspaceToken],\n );\n\n return { upload, ...state };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"usePackageUpload.js","names":[],"sources":["../../../src/devportal/hooks/usePackageUpload.ts"],"sourcesContent":["/**\n * usePackageUpload — uploads a zip package to wspace-files-svc and returns a\n * stable downloadUrl to store as ApplicationVersion.packageUrl.\n *\n * Flow (mirrors uploadBlobToFiles in @burdenoff/fe-libs/storage):\n * 1. ensureFolderByPath(\"/DevPortal/Packages\") → folderId\n * 2. initiateUpload → { sessionId, presignedUrl }\n * 3. PUT blob to presignedUrl (progress tracked via XHR)\n * 4. completeUpload → { file.downloadUrl }\n *\n * Uses raw fetch for GraphQL mutations so we can target the workspace gateway\n * without a second ApolloProvider.\n */\nimport { useCallback, useState } from \"react\";\n\nexport interface PackageUploadResult {\n fileId: string;\n downloadUrl: string;\n}\n\nexport interface PackageUploadState {\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\ntype GqlFetcher = (\n query: string,\n variables: Record<string, unknown>,\n) => Promise<Record<string, unknown>>;\n\nfunction makeFetcher(\n workspacesGatewayUrl: string,\n authToken: string | undefined,\n workspaceId: string | undefined,\n workspaceToken: string | undefined,\n): GqlFetcher {\n return async (query, variables) => {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (authToken) headers[\"Authorization\"] = `Bearer ${authToken}`;\n if (workspaceToken)\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n if (workspaceId) headers[\"x-workspace-id\"] = workspaceId;\n\n const endpoint = workspacesGatewayUrl.endsWith(\"/graphql\")\n ? workspacesGatewayUrl\n : `${workspacesGatewayUrl}/workspaces/graphql`;\n\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers,\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n if (!res.ok) {\n throw new Error(\n `GraphQL request failed: ${res.status} ${res.statusText}`,\n );\n }\n const json = (await res.json()) as {\n data?: Record<string, unknown>;\n errors?: Array<{ message: string }>;\n };\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n return json.data ?? {};\n };\n}\n\nasync function ensureFolder(gql: GqlFetcher, path: string): Promise<string> {\n const data = await gql(\n `mutation DevPortal_EnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }`,\n { path },\n );\n const id = (data.ensureFolderByPath as { id?: string } | undefined)?.id;\n if (!id) throw new Error(\"ensureFolderByPath returned no id\");\n return id;\n}\n\nasync function initiateUpload(\n gql: GqlFetcher,\n file: File,\n folderId: string,\n): Promise<{ sessionId: string; uploadUrl: string }> {\n const ext = file.name.includes(\".\")\n ? file.name.substring(file.name.lastIndexOf(\".\") + 1)\n : \"zip\";\n const data = await gql(\n `mutation DevPortal_InitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || \"application/zip\",\n extension: ext,\n folderId,\n visibility: \"PUBLIC\",\n },\n },\n );\n const result = data.initiateUpload as\n | { uploadUrl?: string; session?: { id?: string } }\n | undefined;\n const sessionId = result?.session?.id;\n const uploadUrl = result?.uploadUrl;\n if (!sessionId || !uploadUrl) {\n throw new Error(\"initiateUpload did not return sessionId or uploadUrl\");\n }\n return { sessionId, uploadUrl };\n}\n\nasync function putToPresignedUrl(\n file: File,\n uploadUrl: string,\n onProgress: (pct: number) => void,\n): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.upload.addEventListener(\"progress\", (evt) => {\n if (evt.lengthComputable) {\n onProgress(10 + Math.round((evt.loaded / evt.total) * 80));\n }\n });\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`S3 PUT failed: ${xhr.status}`));\n }\n });\n xhr.addEventListener(\"error\", () =>\n reject(new Error(\"S3 PUT network error\")),\n );\n xhr.addEventListener(\"abort\", () => reject(new Error(\"S3 PUT aborted\")));\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type || \"application/zip\");\n xhr.send(file);\n });\n}\n\nasync function completeUpload(\n gql: GqlFetcher,\n sessionId: string,\n folderId: string,\n fileSize: number,\n): Promise<PackageUploadResult> {\n const data = await gql(\n `mutation DevPortal_CompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n file { id downloadUrl }\n }\n }`,\n {\n input: {\n sessionId,\n folderId,\n fileSize: String(fileSize),\n },\n },\n );\n const file = (\n data.completeUpload as\n | { file?: { id?: string; downloadUrl?: string } }\n | undefined\n )?.file;\n if (!file?.id || !file.downloadUrl) {\n throw new Error(\"completeUpload did not return file id or downloadUrl\");\n }\n return { fileId: file.id, downloadUrl: file.downloadUrl };\n}\n\nexport function usePackageUpload(\n workspacesGatewayUrl: string | undefined,\n authToken: string | undefined,\n workspaceId: string | undefined,\n getWorkspaceToken: (() => Promise<string | null>) | undefined,\n folderPath = \"/DevPortal/Packages\",\n) {\n const [state, setState] = useState<PackageUploadState>({\n uploading: false,\n progress: 0,\n error: null,\n });\n\n const upload = useCallback(\n async (file: File): Promise<PackageUploadResult> => {\n if (!workspacesGatewayUrl) {\n throw new Error(\n \"workspacesGatewayUrl is not configured. Pass it as a prop to DevportalRoot.\",\n );\n }\n\n const freshToken = getWorkspaceToken\n ? await getWorkspaceToken()\n : undefined;\n const gql = makeFetcher(\n workspacesGatewayUrl,\n authToken,\n workspaceId,\n freshToken ?? undefined,\n );\n\n setState({ uploading: true, progress: 0, error: null });\n try {\n const folderId = await ensureFolder(gql, folderPath);\n setState((s) => ({ ...s, progress: 5 }));\n\n const { sessionId, uploadUrl } = await initiateUpload(\n gql,\n file,\n folderId,\n );\n setState((s) => ({ ...s, progress: 10 }));\n\n await putToPresignedUrl(file, uploadUrl, (pct) =>\n setState((s) => ({ ...s, progress: pct })),\n );\n setState((s) => ({ ...s, progress: 90 }));\n\n const result = await completeUpload(\n gql,\n sessionId,\n folderId,\n file.size,\n );\n setState({ uploading: false, progress: 100, error: null });\n return result;\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Upload failed\";\n setState({ uploading: false, progress: 0, error: message });\n throw err;\n }\n },\n [workspacesGatewayUrl, authToken, workspaceId, getWorkspaceToken],\n );\n\n return { upload, ...state };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CreateApplicationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateApplicationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAybhC,eAAO,MAAM,qBAAqB,EAAE,EAsjBnC,CAAC"}
|