@burdenoff/microfe-devportal 2026.1110.2 → 2026.1112.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/hooks/permissionRules.d.ts +1 -0
- package/dist/devportal/hooks/permissionRules.js +13 -0
- package/dist/devportal/hooks/permissionRules.js.map +1 -0
- package/dist/devportal/hooks/usePermission.js +9 -6
- package/dist/devportal/hooks/usePermission.js.map +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +120 -114
- package/dist/devportal/pages/CreateOrganizationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +84 -73
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js +134 -115
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/SetupPage.js +141 -143
- package/dist/devportal/pages/SetupPage.js.map +1 -1
- package/dist/devportal/providers/DevportalProvider.d.ts +5 -0
- package/dist/devportal/providers/DevportalProvider.js +33 -32
- package/dist/devportal/providers/DevportalProvider.js.map +1 -1
- package/dist/devportal/types/index.d.ts +6 -0
- package/dist/devportal/types/index.js.map +1 -1
- package/dist/devportal/utils/errorMessage.d.ts +2 -0
- package/dist/devportal/utils/errorMessage.js +7 -0
- package/dist/devportal/utils/errorMessage.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SetupPage.js","names":["generateSlug","name","errorMessage","err","fallback","markSetupSeen","STEPS","StepIndicator","currentIdx","s","current","isCompleted","idx","isActive","Check","step","SetupPage","useDevportal","useI18n","bus","useDevportalEventBus","tr","v","t","key","handleNavigate","navigate","basePath","path","setStep","useState","createdOrgId","setCreatedOrgId","createdAppId","setCreatedAppId","createdAppType","setCreatedAppType","orgName","setOrgName","orgSlug","setOrgSlug","orgDescription","setOrgDescription","orgErrors","setOrgErrors","agreementAccepted","setAgreementAccepted","orgSubmitError","setOrgSubmitError","createOrg","creatingOrgOnly","useCreateOrganizationMutation","createProfile","creatingProfile","useCreateDeveloperProfileMutation","creatingOrg","createBillingAccountMutation","useCreateBillingAccountMutation","validateOrg","errs","handleOrgSubmit","id","currentUser","DEVELOPER_AGREEMENT_VERSION","DEVELOPER_LINKS","appName","setAppName","appSlug","setAppSlug","builderType","setBuilderType","appCategory","setAppCategory","appShortDesc","setAppShortDesc","appErrors","setAppErrors","appSubmitError","setAppSubmitError","createApp","creatingApp","useCreateApplicationMutation","appTypeForBuilder","validateApp","handleAppSubmit","resolvedType","reviewRequirements","reviewRequirementsFor","handleSkip","Rocket","Illustration","PagePurpose","Building2","InputField","p","TextareaField","EligibilityPanel","ChevronRight","AppWindow","SetupTypeCard","Box","Puzzle","GitFork","SelectField","APP_CATEGORY_OPTIONS","ListChecks","item","NextStepCard","Key","BookOpen","ArrowRight","ShieldCheck","ELIGIBILITY_RULES","rule","Icon","cn","selected","title","description","primary","icon"],"sources":["../../../src/devportal/pages/SetupPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport {\n ArrowRight,\n Building2,\n AppWindow,\n Rocket,\n Check,\n Key,\n BookOpen,\n ChevronRight,\n Box,\n Puzzle,\n GitFork,\n ListChecks,\n ShieldCheck,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { Illustration, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { APP_CATEGORY_OPTIONS } from \"../constants/appCategories\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport {\n useCreateOrganizationMutation,\n useCreateApplicationMutation,\n useCreateDeveloperProfileMutation,\n} from \"../../generated/global-operations\";\nimport { useCreateBillingAccountMutation } from \"../hooks\";\nimport {\n DEVELOPER_AGREEMENT_VERSION,\n DEVELOPER_LINKS,\n} from \"../constants/developerLinks\";\nimport {\n ELIGIBILITY_RULES,\n reviewRequirementsFor,\n} from \"../utils/developerOnboarding\";\n\ntype SetupStep = \"org\" | \"app\" | \"submit\" | \"done\";\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\nfunction generateSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/** Readable message from a failed mutation, falling back to a generic one. */\nfunction errorMessage(err: unknown, fallback: string): string {\n if (err instanceof Error && err.message) return err.message;\n return fallback;\n}\n\n/** Mark that the user has interacted with setup so the dashboard gate won't redirect again. */\nfunction markSetupSeen() {\n try {\n localStorage.setItem(\"devportal_setup_dismissed\", \"1\");\n } catch {\n // localStorage unavailable — not critical\n }\n}\n\n// ─── Step indicator ────────────────────────────────────────────────────────\n\nconst STEPS: { key: SetupStep; label: string }[] = [\n { key: \"org\", label: \"Organization\" },\n { key: \"app\", label: \"First app\" },\n { key: \"submit\", label: \"Submit\" },\n { key: \"done\", label: \"Done\" },\n];\n\nconst StepIndicator: FC<{ current: SetupStep }> = ({ current }) => {\n const currentIdx = STEPS.findIndex((s) => s.key === current);\n return (\n <div className=\"flex items-center gap-0\">\n {STEPS.map((step, idx) => {\n const isCompleted = idx < currentIdx;\n const isActive = idx === currentIdx;\n return (\n <div key={step.key} className=\"flex items-center\">\n <div className=\"flex flex-col items-center gap-1.5\">\n <div\n className={[\n \"flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition-colors\",\n isCompleted\n ? \"bg-action-primary-bg text-action-primary-text\"\n : isActive\n ? \"bg-action-primary-bg text-action-primary-text ring-4 ring-primary/20\"\n : \"bg-bg-sunken text-text-secondary\",\n ].join(\" \")}\n >\n {isCompleted ? <Check size={14} /> : idx + 1}\n </div>\n <span\n className={[\n \"text-xs font-medium whitespace-nowrap\",\n isActive ? \"text-text-primary\" : \"text-text-secondary\",\n ].join(\" \")}\n >\n {step.label}\n </span>\n </div>\n {idx < STEPS.length - 1 && (\n <div\n className={[\n \"mx-3 mb-5 h-px w-16 flex-shrink-0 transition-colors\",\n idx < currentIdx ? \"bg-action-primary-bg\" : \"bg-border\",\n ].join(\" \")}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n};\n\n// ─── Main component ────────────────────────────────────────────────────────\n\nexport const SetupPage: FC = () => {\n const { navigate, basePath, currentUser } = useDevportal();\n const { t } = useI18n();\n const bus = useDevportalEventBus();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const handleNavigate = (path: string) => navigate?.(`${basePath}${path}`);\n\n const [step, setStep] = useState<SetupStep>(\"org\");\n\n // IDs created during wizard — carried forward for success CTAs\n const [createdOrgId, setCreatedOrgId] = useState<string | null>(null);\n const [createdAppId, setCreatedAppId] = useState<string | null>(null);\n const [createdAppType, setCreatedAppType] = useState<string | null>(null);\n\n // ── Step 1: org ──────────────────────────────────────────────\n const [orgName, setOrgName] = useState(\"\");\n const [orgSlug, setOrgSlug] = useState(\"\");\n const [orgDescription, setOrgDescription] = useState(\"\");\n const [orgErrors, setOrgErrors] = useState<{\n name?: string;\n slug?: string;\n agreement?: string;\n }>({});\n const [agreementAccepted, setAgreementAccepted] = useState(false);\n const [orgSubmitError, setOrgSubmitError] = useState<string | null>(null);\n\n const [createOrg, { loading: creatingOrgOnly }] =\n useCreateOrganizationMutation();\n const [createProfile, { loading: creatingProfile }] =\n useCreateDeveloperProfileMutation();\n const creatingOrg = creatingOrgOnly || creatingProfile;\n const [createBillingAccountMutation] = useCreateBillingAccountMutation();\n\n const validateOrg = () => {\n const errs: { name?: string; slug?: string; agreement?: string } = {};\n if (!orgName.trim())\n errs.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n else if (orgName.length < 2)\n errs.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n if (!orgSlug.trim())\n errs.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n else if (!/^[a-z0-9-]+$/.test(orgSlug))\n errs.slug = tr(\n \"devportal.slugInvalid\",\n \"Lowercase letters, numbers, and hyphens only\",\n );\n if (!agreementAccepted)\n errs.agreement = tr(\n \"devportal.onboarding.agreementRequired\",\n \"Accept the Developer Agreement to continue\",\n );\n setOrgErrors(errs);\n return Object.keys(errs).length === 0;\n };\n\n const handleOrgSubmit = async () => {\n if (!validateOrg()) return;\n setOrgSubmitError(null);\n try {\n // The developer profile is the personal half of the registration: the\n // backend returns the existing one when it is already there, so this is\n // safe to repeat on a retry.\n await createProfile({\n variables: { input: { companyName: orgName } },\n });\n const result = await createOrg({\n variables: {\n input: {\n name: orgName,\n slug: orgSlug || undefined,\n description: orgDescription || undefined,\n contactEmail: currentUser?.email || undefined,\n metadata: {\n developerAgreement: {\n version: DEVELOPER_AGREEMENT_VERSION,\n url: DEVELOPER_LINKS.developerAgreement,\n acceptedAt: new Date().toISOString(),\n acceptedBy: currentUser?.id ?? null,\n },\n registrationSource: \"devportal-setup\",\n },\n },\n },\n });\n const id = result.data?.createDeveloperOrganization?.id;\n if (id) {\n bus.emit(\"devportal.org.created\", {\n organizationId: id,\n name: orgName,\n });\n setCreatedOrgId(id);\n // Fire-and-forget: create a billing account for the new org\n try {\n createBillingAccountMutation({\n variables: {\n input: {\n name: orgName,\n email: currentUser?.email ?? \"\",\n },\n },\n }).catch(() => {});\n } catch {\n // silent failure — billing account creation is non-blocking\n }\n setStep(\"app\");\n }\n } catch (err) {\n setOrgSubmitError(\n errorMessage(\n err,\n tr(\n \"devportal.onboarding.orgCreateFailed\",\n \"We could not create your organization. Please try again.\",\n ),\n ),\n );\n }\n };\n\n // ── Step 2: app ──────────────────────────────────────────────\n const [appName, setAppName] = useState(\"\");\n const [appSlug, setAppSlug] = useState(\"\");\n const [builderType, setBuilderType] = useState<BuilderType | null>(null);\n const [appCategory, setAppCategory] = useState(\"\");\n const [appShortDesc, setAppShortDesc] = useState(\"\");\n const [appErrors, setAppErrors] = useState<{\n name?: string;\n builderType?: string;\n category?: string;\n shortDescription?: string;\n }>({});\n\n const [appSubmitError, setAppSubmitError] = useState<string | null>(null);\n const [createApp, { loading: creatingApp }] = useCreateApplicationMutation();\n\n const appTypeForBuilder = () => {\n if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const validateApp = () => {\n const errs: typeof appErrors = {};\n if (!appName.trim())\n errs.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n else if (appName.length < 3)\n errs.name = tr(\n \"devportal.nameTooShort\",\n \"Name must be at least 3 characters\",\n );\n if (!builderType)\n errs.builderType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n if (!appCategory)\n errs.category = tr(\"devportal.categoryRequired\", \"Category is required\");\n if (!appShortDesc.trim())\n errs.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n setAppErrors(errs);\n return Object.keys(errs).length === 0;\n };\n\n const handleAppSubmit = async () => {\n if (!validateApp()) return;\n const resolvedType = appTypeForBuilder();\n setAppSubmitError(null);\n try {\n const result = await createApp({\n variables: {\n input: {\n name: appName,\n slug: appSlug || undefined,\n appType: resolvedType,\n category: appCategory || undefined,\n shortDescription: appShortDesc || undefined,\n visibility: \"PRIVATE\",\n developerOrganizationId: createdOrgId || undefined,\n },\n },\n });\n const id = result.data?.createApplication?.id;\n if (id) {\n bus.emit(\"devportal.app.created\", {\n appId: id,\n name: appName,\n appType: resolvedType,\n organizationId: createdOrgId,\n });\n setCreatedAppId(id);\n setCreatedAppType(resolvedType);\n markSetupSeen();\n setStep(\"submit\");\n }\n } catch (err) {\n setAppSubmitError(\n errorMessage(\n err,\n tr(\n \"devportal.onboarding.appCreateFailed\",\n \"We could not create your app. Please try again.\",\n ),\n ),\n );\n }\n };\n\n // ── Step 3: get it ready for review ──────────────────────────\n // Submission itself happens on the app page, which checks the listing\n // requirements reviewers need (ApplicationDetailPage's readiness rules);\n // this step tells the developer what that page will ask for.\n const reviewRequirements = reviewRequirementsFor(createdAppType);\n\n const handleSkip = () => {\n markSetupSeen();\n handleNavigate(\"/\");\n };\n\n // ─── Render ─────────────────────────────────────────────────\n\n return (\n <main className=\"min-h-screen bg-surface-default\">\n {/* Header */}\n <div className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-2xl px-6 py-5\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Rocket size={20} />\n </div>\n <div>\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.developerPortal\", \"Developer Portal\")}\n </p>\n <h1 className=\"text-base font-semibold text-text-primary\">\n {tr(\"devportal.setup.title\", \"Get set up\")}\n </h1>\n </div>\n </div>\n {step !== \"done\" && (\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"mx-auto max-w-2xl px-6 py-10\">\n {/* Step indicator */}\n <div className=\"mb-10 flex justify-center\">\n <StepIndicator current={step} />\n </div>\n\n {/* Welcome / purpose — what this wizard sets up and why */}\n {step === \"org\" && (\n <div className=\"mb-8 flex flex-col items-center gap-3 text-center\">\n <Illustration\n name=\"onboarding\"\n alt={tr(\n \"devportal.setup.illustrationAlt\",\n \"Getting started with the developer portal\",\n )}\n size=\"md\"\n />\n <PagePurpose className=\"justify-center\">\n {tr(\n \"devportal.setup.purposeWithReview\",\n \"Three quick steps to start building: create the organization that owns your apps, register your first app, then submit it for review. Your developer profile is created with your organization. You can change everything later.\",\n )}\n </PagePurpose>\n </div>\n )}\n\n {/* ── Step 1: Organization ─────────────────────── */}\n {step === \"org\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"devportal.setup.orgTitle\", \"Set up your organization\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.setup.orgSubtitle\",\n \"This groups your apps and teams under one publisher identity.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-5\">\n <InputField\n label={tr(\"devportal.organizationName\", \"Organization Name\")}\n value={orgName}\n onChange={(e) => {\n setOrgName(e.target.value);\n if (!orgSlug || orgSlug === generateSlug(orgName)) {\n setOrgSlug(generateSlug(e.target.value));\n }\n setOrgErrors((p) => ({ ...p, name: undefined }));\n }}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={orgErrors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={orgSlug}\n onChange={(e) => {\n setOrgSlug(e.target.value.toLowerCase());\n setOrgErrors((p) => ({ ...p, slug: undefined }));\n }}\n placeholder=\"acme-dev\"\n required\n error={orgErrors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier — auto-generated from name\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={orgDescription}\n onChange={(e) => setOrgDescription(e.target.value)}\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization…\",\n )}\n rows={2}\n />\n\n <EligibilityPanel tr={tr} />\n\n <div>\n <label className=\"flex items-start gap-3 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n data-testid=\"devportal-setup-accept-agreement\"\n checked={agreementAccepted}\n onChange={(e) => {\n setAgreementAccepted(e.target.checked);\n setOrgErrors((p) => ({ ...p, agreement: undefined }));\n }}\n className=\"mt-0.5 h-4 w-4 shrink-0 rounded border-border-default accent-primary\"\n />\n <span>\n {tr(\n \"devportal.onboarding.agreementLabelPrefix\",\n \"I meet these requirements and accept the\",\n )}{\" \"}\n <a\n href={DEVELOPER_LINKS.developerAgreement}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-primary hover:underline\"\n >\n {tr(\n \"devportal.onboarding.developerAgreement\",\n \"Marketplace & Developer Agreement\",\n )}\n </a>{\" \"}\n {tr(\"devportal.onboarding.and\", \"and the\")}{\" \"}\n <a\n href={DEVELOPER_LINKS.acceptableUsePolicy}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-primary hover:underline\"\n >\n {tr(\n \"devportal.onboarding.acceptableUsePolicy\",\n \"Acceptable Use Policy\",\n )}\n </a>\n .\n </span>\n </label>\n {orgErrors.agreement && (\n <p className=\"mt-1.5 text-xs text-status-error-text\">\n {orgErrors.agreement}\n </p>\n )}\n </div>\n\n {orgSubmitError && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-text\"\n >\n {orgSubmitError}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleOrgSubmit()}\n disabled={creatingOrg}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-60 transition-colors\"\n >\n {creatingOrg ? (\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n ) : null}\n {tr(\"devportal.setup.createAndContinue\", \"Create & Continue\")}\n {!creatingOrg && <ChevronRight size={16} />}\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 2: First app ─────────────────────── */}\n {step === \"app\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"devportal.setup.appTitle\", \"Create your first app\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.setup.appSubtitle\",\n \"Register the app you want to publish or integrate.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-5\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={appName}\n onChange={(e) => {\n setAppName(e.target.value);\n if (!appSlug || appSlug === generateSlug(appName)) {\n setAppSlug(generateSlug(e.target.value));\n }\n setAppErrors((p) => ({ ...p, name: undefined }));\n }}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={appErrors.name}\n />\n\n {/* Builder type picker */}\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">\n {tr(\"devportal.applicationType\", \"Application Type\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <SetupTypeCard\n icon={Box}\n title=\"App\"\n description=\"OAuth integration for the platform\"\n selected={builderType === \"app\"}\n onClick={() => {\n setBuilderType(\"app\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n <SetupTypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Custom FluidGrids workflow node\"\n selected={builderType === \"node\"}\n onClick={() => {\n setBuilderType(\"node\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n <SetupTypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Reusable automation template\"\n selected={builderType === \"workflow\"}\n onClick={() => {\n setBuilderType(\"workflow\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n </div>\n {appErrors.builderType && (\n <p className=\"mt-1.5 text-xs text-status-error-text\">\n {appErrors.builderType}\n </p>\n )}\n </div>\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={appCategory}\n onChange={(e) => {\n setAppCategory(e.target.value);\n setAppErrors((p) => ({ ...p, category: undefined }));\n }}\n options={[...APP_CATEGORY_OPTIONS]}\n placeholder={tr(\"devportal.selectCategory\", \"Select category…\")}\n required\n error={appErrors.category}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={appShortDesc}\n onChange={(e) => {\n setAppShortDesc(e.target.value);\n setAppErrors((p) => ({ ...p, shortDescription: undefined }));\n }}\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={appErrors.shortDescription}\n rows={2}\n hint={`${appShortDesc.length}/200`}\n />\n\n {appSubmitError && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-text\"\n >\n {appSubmitError}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAppSubmit()}\n disabled={creatingApp}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-60 transition-colors\"\n >\n {creatingApp ? (\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n ) : null}\n {tr(\"devportal.setup.createApp\", \"Create App\")}\n {!creatingApp && <ChevronRight size={16} />}\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 3: Get ready for review ─────────── */}\n {step === \"submit\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <ListChecks size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\n \"devportal.onboarding.readyTitle\",\n \"Get it ready for review\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.onboarding.readySubtitle\",\n \"Reviewers check every app before it is listed. Your app page walks you through what they need, then you submit it from there. It stays private until it is approved and you publish it.\",\n )}\n </p>\n </div>\n </div>\n\n <ul\n data-testid=\"devportal-setup-review-requirements\"\n className=\"space-y-2\"\n >\n {reviewRequirements.map((item) => (\n <li\n key={item.id}\n className=\"flex items-start gap-3 rounded-md bg-bg-sunken px-4 py-3 text-sm text-text-primary\"\n >\n <Check\n size={16}\n className=\"mt-0.5 shrink-0 text-text-secondary\"\n aria-hidden=\"true\"\n />\n {tr(item.key, item.text)}\n </li>\n ))}\n </ul>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={() => setStep(\"done\")}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.onboarding.submitLater\", \"Do this later\")}\n </button>\n <button\n type=\"button\"\n data-testid=\"devportal-setup-finish-app\"\n onClick={() =>\n createdAppId && handleNavigate(`/apps/${createdAppId}`)\n }\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr(\"devportal.onboarding.finishApp\", \"Finish and submit\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 4: Done ─────────────────────────── */}\n {step === \"done\" && (\n <div className=\"text-center\">\n <div className=\"mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-action-primary-bg/10 text-primary\">\n <Rocket size={32} />\n </div>\n <h2 className=\"text-2xl font-bold text-text-primary\">\n {tr(\"devportal.setup.doneTitle\", \"You're all set!\")}\n </h2>\n <p className=\"mt-2 text-text-secondary\">\n {tr(\n \"devportal.setup.doneSubtitle\",\n \"Your organization and first app are ready. Here's what to do next.\",\n )}\n </p>\n\n <div className=\"mt-8 grid gap-3 sm:grid-cols-2\">\n {createdAppId && (\n <NextStepCard\n icon={<AppWindow size={20} />}\n title={tr(\"devportal.setup.viewApp\", \"View your app\")}\n description={tr(\n \"devportal.setup.viewAppDesc\",\n \"Configure OAuth, webhooks, and versioning\",\n )}\n onClick={() => handleNavigate(`/apps/${createdAppId}`)}\n primary\n />\n )}\n <NextStepCard\n icon={<Key size={20} />}\n title={tr(\"devportal.setup.generateApiKey\", \"Generate API key\")}\n description={tr(\n \"devportal.setup.generateApiKeyDesc\",\n \"Create credentials to start calling APIs\",\n )}\n onClick={() => handleNavigate(\"/api-keys/new\")}\n />\n <NextStepCard\n icon={<BookOpen size={20} />}\n title={tr(\"devportal.setup.browseDocs\", \"Browse docs\")}\n description={tr(\n \"devportal.setup.browseDocsDesc\",\n \"Learn about publishing, SDKs, and webhooks\",\n )}\n onClick={() => handleNavigate(\"/docs\")}\n />\n <NextStepCard\n icon={<Puzzle size={20} />}\n title={tr(\n \"devportal.onboarding.publishConnector\",\n \"Publish a connector\",\n )}\n description={tr(\n \"devportal.onboarding.publishConnectorDesc\",\n \"List an API-key integration in the Workspaces integrations catalog\",\n )}\n onClick={() =>\n window.open(\n DEVELOPER_LINKS.publishConnector,\n \"_blank\",\n \"noopener,noreferrer\",\n )\n }\n />\n {createdOrgId && (\n <NextStepCard\n icon={<Building2 size={20} />}\n title={tr(\"devportal.setup.inviteTeam\", \"Invite your team\")}\n description={tr(\n \"devportal.setup.inviteTeamDesc\",\n \"Add members to collaborate on your apps\",\n )}\n onClick={() =>\n handleNavigate(`/organizations/${createdOrgId}/members`)\n }\n />\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/\")}\n className=\"mt-8 inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.goToDashboard\", \"Go to dashboard\")}\n <ArrowRight size={14} />\n </button>\n </div>\n )}\n </div>\n </main>\n );\n};\n\n// ─── Eligibility panel ─────────────────────────────────────────────────────\n\nconst EligibilityPanel: FC<{\n tr: (key: string, fallback: string) => string;\n}> = ({ tr }) => (\n <section\n aria-labelledby=\"devportal-setup-eligibility-title\"\n data-testid=\"devportal-setup-eligibility\"\n className=\"rounded-lg border border-border-subtle bg-bg-sunken p-4\"\n >\n <h3\n id=\"devportal-setup-eligibility-title\"\n className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\"\n >\n <ShieldCheck size={16} className=\"text-primary\" aria-hidden=\"true\" />\n {tr(\"devportal.onboarding.eligibilityTitle\", \"Who can register\")}\n </h3>\n <ul className=\"mt-2 list-disc space-y-1 pl-5 text-sm text-text-secondary\">\n {ELIGIBILITY_RULES.map((rule) => (\n <li key={rule.id}>{tr(rule.key, rule.text)}</li>\n ))}\n </ul>\n </section>\n);\n\n// ─── Setup type card ───────────────────────────────────────────────────────\n\nconst SetupTypeCard: FC<{\n icon: FC<{ size?: number; className?: string }>;\n title: string;\n description: string;\n selected: boolean;\n onClick: () => void;\n}> = ({ icon: Icon, title, description, selected, onClick }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative flex flex-col gap-2 rounded-xl border-2 p-4 text-left transition-all hover:border-primary/50 hover:shadow-sm\",\n selected\n ? \"border-primary bg-action-primary-bg/5 shadow-sm\"\n : \"border-border-subtle bg-bg-surface\",\n )}\n >\n {selected && (\n <span className=\"absolute top-3 right-3 flex h-4 w-4 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text\">\n <Check size={10} />\n </span>\n )}\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon size={18} className=\"text-text-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// ─── Next-step card ────────────────────────────────────────────────────────\n\nconst NextStepCard: FC<{\n icon: React.ReactNode;\n title: string;\n description: string;\n onClick: () => void;\n primary?: boolean;\n}> = ({ icon, title, description, onClick, primary }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={[\n \"flex items-start gap-4 rounded-xl border p-5 text-left transition-colors\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-default bg-surface-raised hover:bg-surface-hover\",\n ].join(\" \")}\n >\n <div\n className={[\n \"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg\",\n primary\n ? \"bg-action-primary-bg/20 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>\n </div>\n <ChevronRight\n size={16}\n className=\"mt-0.5 flex-shrink-0 text-text-secondary\"\n />\n </button>\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SetupPage.js","names":["generateSlug","name","markSetupSeen","STEPS","StepIndicator","currentIdx","s","current","isCompleted","idx","isActive","Check","step","SetupPage","useDevportal","useI18n","bus","useDevportalEventBus","tr","v","t","key","fallback","handleNavigate","navigate","basePath","path","setStep","useState","createdOrgId","setCreatedOrgId","createdAppId","setCreatedAppId","createdAppType","setCreatedAppType","orgName","setOrgName","orgSlug","setOrgSlug","orgDescription","setOrgDescription","orgErrors","setOrgErrors","agreementAccepted","setAgreementAccepted","orgSubmitError","setOrgSubmitError","createOrg","creatingOrgOnly","useCreateOrganizationMutation","createProfile","creatingProfile","useCreateDeveloperProfileMutation","creatingOrg","createBillingAccountMutation","useCreateBillingAccountMutation","validateOrg","errs","handleOrgSubmit","id","currentUser","DEVELOPER_AGREEMENT_VERSION","DEVELOPER_LINKS","err","errorMessage","appName","setAppName","appSlug","setAppSlug","builderType","setBuilderType","appCategory","setAppCategory","appShortDesc","setAppShortDesc","appErrors","setAppErrors","appSubmitError","setAppSubmitError","createApp","creatingApp","useCreateApplicationMutation","appTypeForBuilder","validateApp","handleAppSubmit","resolvedType","reviewRequirements","reviewRequirementsFor","handleSkip","Rocket","Illustration","PagePurpose","Building2","InputField","p","TextareaField","EligibilityPanel","ChevronRight","AppWindow","SetupTypeCard","Box","Puzzle","GitFork","SelectField","APP_CATEGORY_OPTIONS","ListChecks","item","NextStepCard","Key","BookOpen","ArrowRight","ShieldCheck","ELIGIBILITY_RULES","rule","Icon","cn","selected","title","description","primary","icon"],"sources":["../../../src/devportal/pages/SetupPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport {\n ArrowRight,\n Building2,\n AppWindow,\n Rocket,\n Check,\n Key,\n BookOpen,\n ChevronRight,\n Box,\n Puzzle,\n GitFork,\n ListChecks,\n ShieldCheck,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { Illustration, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { APP_CATEGORY_OPTIONS } from \"../constants/appCategories\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { useDevportalEventBus } from \"../events\";\nimport { cn } from \"../utils/cn\";\nimport { errorMessage } from \"../utils/errorMessage\";\nimport {\n useCreateOrganizationMutation,\n useCreateApplicationMutation,\n useCreateDeveloperProfileMutation,\n} from \"../../generated/global-operations\";\nimport { useCreateBillingAccountMutation } from \"../hooks\";\nimport {\n DEVELOPER_AGREEMENT_VERSION,\n DEVELOPER_LINKS,\n} from \"../constants/developerLinks\";\nimport {\n ELIGIBILITY_RULES,\n reviewRequirementsFor,\n} from \"../utils/developerOnboarding\";\n\ntype SetupStep = \"org\" | \"app\" | \"submit\" | \"done\";\ntype BuilderType = \"app\" | \"node\" | \"workflow\";\n\nfunction generateSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/** Mark that the user has interacted with setup so the dashboard gate won't redirect again. */\nfunction markSetupSeen() {\n try {\n localStorage.setItem(\"devportal_setup_dismissed\", \"1\");\n } catch {\n // localStorage unavailable — not critical\n }\n}\n\n// ─── Step indicator ────────────────────────────────────────────────────────\n\nconst STEPS: { key: SetupStep; label: string }[] = [\n { key: \"org\", label: \"Organization\" },\n { key: \"app\", label: \"First app\" },\n { key: \"submit\", label: \"Submit\" },\n { key: \"done\", label: \"Done\" },\n];\n\nconst StepIndicator: FC<{ current: SetupStep }> = ({ current }) => {\n const currentIdx = STEPS.findIndex((s) => s.key === current);\n return (\n <div className=\"flex items-center gap-0\">\n {STEPS.map((step, idx) => {\n const isCompleted = idx < currentIdx;\n const isActive = idx === currentIdx;\n return (\n <div key={step.key} className=\"flex items-center\">\n <div className=\"flex flex-col items-center gap-1.5\">\n <div\n className={[\n \"flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold transition-colors\",\n isCompleted\n ? \"bg-action-primary-bg text-action-primary-text\"\n : isActive\n ? \"bg-action-primary-bg text-action-primary-text ring-4 ring-primary/20\"\n : \"bg-bg-sunken text-text-secondary\",\n ].join(\" \")}\n >\n {isCompleted ? <Check size={14} /> : idx + 1}\n </div>\n <span\n className={[\n \"text-xs font-medium whitespace-nowrap\",\n isActive ? \"text-text-primary\" : \"text-text-secondary\",\n ].join(\" \")}\n >\n {step.label}\n </span>\n </div>\n {idx < STEPS.length - 1 && (\n <div\n className={[\n \"mx-3 mb-5 h-px w-16 flex-shrink-0 transition-colors\",\n idx < currentIdx ? \"bg-action-primary-bg\" : \"bg-border\",\n ].join(\" \")}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n};\n\n// ─── Main component ────────────────────────────────────────────────────────\n\nexport const SetupPage: FC = () => {\n const { navigate, basePath, currentUser } = useDevportal();\n const { t } = useI18n();\n const bus = useDevportalEventBus();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const handleNavigate = (path: string) => navigate?.(`${basePath}${path}`);\n\n const [step, setStep] = useState<SetupStep>(\"org\");\n\n // IDs created during wizard — carried forward for success CTAs\n const [createdOrgId, setCreatedOrgId] = useState<string | null>(null);\n const [createdAppId, setCreatedAppId] = useState<string | null>(null);\n const [createdAppType, setCreatedAppType] = useState<string | null>(null);\n\n // ── Step 1: org ──────────────────────────────────────────────\n const [orgName, setOrgName] = useState(\"\");\n const [orgSlug, setOrgSlug] = useState(\"\");\n const [orgDescription, setOrgDescription] = useState(\"\");\n const [orgErrors, setOrgErrors] = useState<{\n name?: string;\n slug?: string;\n agreement?: string;\n }>({});\n const [agreementAccepted, setAgreementAccepted] = useState(false);\n const [orgSubmitError, setOrgSubmitError] = useState<string | null>(null);\n\n const [createOrg, { loading: creatingOrgOnly }] =\n useCreateOrganizationMutation();\n const [createProfile, { loading: creatingProfile }] =\n useCreateDeveloperProfileMutation();\n const creatingOrg = creatingOrgOnly || creatingProfile;\n const [createBillingAccountMutation] = useCreateBillingAccountMutation();\n\n const validateOrg = () => {\n const errs: { name?: string; slug?: string; agreement?: string } = {};\n if (!orgName.trim())\n errs.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n else if (orgName.length < 2)\n errs.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n if (!orgSlug.trim())\n errs.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n else if (!/^[a-z0-9-]+$/.test(orgSlug))\n errs.slug = tr(\n \"devportal.slugInvalid\",\n \"Lowercase letters, numbers, and hyphens only\",\n );\n if (!agreementAccepted)\n errs.agreement = tr(\n \"devportal.onboarding.agreementRequired\",\n \"Accept the Developer Agreement to continue\",\n );\n setOrgErrors(errs);\n return Object.keys(errs).length === 0;\n };\n\n const handleOrgSubmit = async () => {\n if (!validateOrg()) return;\n setOrgSubmitError(null);\n try {\n // The developer profile is the personal half of the registration: the\n // backend returns the existing one when it is already there, so this is\n // safe to repeat on a retry.\n await createProfile({\n variables: { input: { companyName: orgName } },\n });\n const result = await createOrg({\n variables: {\n input: {\n name: orgName,\n slug: orgSlug || undefined,\n description: orgDescription || undefined,\n contactEmail: currentUser?.email || undefined,\n metadata: {\n developerAgreement: {\n version: DEVELOPER_AGREEMENT_VERSION,\n url: DEVELOPER_LINKS.developerAgreement,\n acceptedAt: new Date().toISOString(),\n acceptedBy: currentUser?.id ?? null,\n },\n registrationSource: \"devportal-setup\",\n },\n },\n },\n });\n const id = result.data?.createDeveloperOrganization?.id;\n if (id) {\n bus.emit(\"devportal.org.created\", {\n organizationId: id,\n name: orgName,\n });\n setCreatedOrgId(id);\n // Fire-and-forget: create a billing account for the new org\n try {\n createBillingAccountMutation({\n variables: {\n input: {\n name: orgName,\n email: currentUser?.email ?? \"\",\n },\n },\n }).catch(() => {});\n } catch {\n // silent failure — billing account creation is non-blocking\n }\n setStep(\"app\");\n }\n } catch (err) {\n setOrgSubmitError(\n errorMessage(\n err,\n tr(\n \"devportal.onboarding.orgCreateFailed\",\n \"We could not create your organization. Please try again.\",\n ),\n ),\n );\n }\n };\n\n // ── Step 2: app ──────────────────────────────────────────────\n const [appName, setAppName] = useState(\"\");\n const [appSlug, setAppSlug] = useState(\"\");\n const [builderType, setBuilderType] = useState<BuilderType | null>(null);\n const [appCategory, setAppCategory] = useState(\"\");\n const [appShortDesc, setAppShortDesc] = useState(\"\");\n const [appErrors, setAppErrors] = useState<{\n name?: string;\n builderType?: string;\n category?: string;\n shortDescription?: string;\n }>({});\n\n const [appSubmitError, setAppSubmitError] = useState<string | null>(null);\n const [createApp, { loading: creatingApp }] = useCreateApplicationMutation();\n\n const appTypeForBuilder = () => {\n if (builderType === \"node\") return \"NODE\" as const;\n if (builderType === \"workflow\") return \"WORKFLOW\" as const;\n return \"API_INTEGRATION\" as const;\n };\n\n const validateApp = () => {\n const errs: typeof appErrors = {};\n if (!appName.trim())\n errs.name = tr(\n \"devportal.appNameRequired\",\n \"Application name is required\",\n );\n else if (appName.length < 3)\n errs.name = tr(\n \"devportal.nameTooShort\",\n \"Name must be at least 3 characters\",\n );\n if (!builderType)\n errs.builderType = tr(\n \"devportal.appTypeRequired\",\n \"Application type is required\",\n );\n if (!appCategory)\n errs.category = tr(\"devportal.categoryRequired\", \"Category is required\");\n if (!appShortDesc.trim())\n errs.shortDescription = tr(\n \"devportal.shortDescriptionRequired\",\n \"Short description is required\",\n );\n setAppErrors(errs);\n return Object.keys(errs).length === 0;\n };\n\n const handleAppSubmit = async () => {\n if (!validateApp()) return;\n const resolvedType = appTypeForBuilder();\n setAppSubmitError(null);\n try {\n const result = await createApp({\n variables: {\n input: {\n name: appName,\n slug: appSlug || undefined,\n appType: resolvedType,\n category: appCategory || undefined,\n shortDescription: appShortDesc || undefined,\n visibility: \"PRIVATE\",\n developerOrganizationId: createdOrgId || undefined,\n },\n },\n });\n const id = result.data?.createApplication?.id;\n if (id) {\n bus.emit(\"devportal.app.created\", {\n appId: id,\n name: appName,\n appType: resolvedType,\n organizationId: createdOrgId,\n });\n setCreatedAppId(id);\n setCreatedAppType(resolvedType);\n markSetupSeen();\n setStep(\"submit\");\n }\n } catch (err) {\n setAppSubmitError(\n errorMessage(\n err,\n tr(\n \"devportal.onboarding.appCreateFailed\",\n \"We could not create your app. Please try again.\",\n ),\n ),\n );\n }\n };\n\n // ── Step 3: get it ready for review ──────────────────────────\n // Submission itself happens on the app page, which checks the listing\n // requirements reviewers need (ApplicationDetailPage's readiness rules);\n // this step tells the developer what that page will ask for.\n const reviewRequirements = reviewRequirementsFor(createdAppType);\n\n const handleSkip = () => {\n markSetupSeen();\n handleNavigate(\"/\");\n };\n\n // ─── Render ─────────────────────────────────────────────────\n\n return (\n <main className=\"min-h-screen bg-surface-default\">\n {/* Header */}\n <div className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-2xl px-6 py-5\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Rocket size={20} />\n </div>\n <div>\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.developerPortal\", \"Developer Portal\")}\n </p>\n <h1 className=\"text-base font-semibold text-text-primary\">\n {tr(\"devportal.setup.title\", \"Get set up\")}\n </h1>\n </div>\n </div>\n {step !== \"done\" && (\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"mx-auto max-w-2xl px-6 py-10\">\n {/* Step indicator */}\n <div className=\"mb-10 flex justify-center\">\n <StepIndicator current={step} />\n </div>\n\n {/* Welcome / purpose — what this wizard sets up and why */}\n {step === \"org\" && (\n <div className=\"mb-8 flex flex-col items-center gap-3 text-center\">\n <Illustration\n name=\"onboarding\"\n alt={tr(\n \"devportal.setup.illustrationAlt\",\n \"Getting started with the developer portal\",\n )}\n size=\"md\"\n />\n <PagePurpose className=\"justify-center\">\n {tr(\n \"devportal.setup.purposeWithReview\",\n \"Three quick steps to start building: create the organization that owns your apps, register your first app, then submit it for review. Your developer profile is created with your organization. You can change everything later.\",\n )}\n </PagePurpose>\n </div>\n )}\n\n {/* ── Step 1: Organization ─────────────────────── */}\n {step === \"org\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"devportal.setup.orgTitle\", \"Set up your organization\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.setup.orgSubtitle\",\n \"This groups your apps and teams under one publisher identity.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-5\">\n <InputField\n label={tr(\"devportal.organizationName\", \"Organization Name\")}\n value={orgName}\n onChange={(e) => {\n setOrgName(e.target.value);\n if (!orgSlug || orgSlug === generateSlug(orgName)) {\n setOrgSlug(generateSlug(e.target.value));\n }\n setOrgErrors((p) => ({ ...p, name: undefined }));\n }}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={orgErrors.name}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={orgSlug}\n onChange={(e) => {\n setOrgSlug(e.target.value.toLowerCase());\n setOrgErrors((p) => ({ ...p, slug: undefined }));\n }}\n placeholder=\"acme-dev\"\n required\n error={orgErrors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier — auto-generated from name\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={orgDescription}\n onChange={(e) => setOrgDescription(e.target.value)}\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization…\",\n )}\n rows={2}\n />\n\n <EligibilityPanel tr={tr} />\n\n <div>\n <label className=\"flex items-start gap-3 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n data-testid=\"devportal-setup-accept-agreement\"\n checked={agreementAccepted}\n onChange={(e) => {\n setAgreementAccepted(e.target.checked);\n setOrgErrors((p) => ({ ...p, agreement: undefined }));\n }}\n className=\"mt-0.5 h-4 w-4 shrink-0 rounded border-border-default accent-primary\"\n />\n <span>\n {tr(\n \"devportal.onboarding.agreementLabelPrefix\",\n \"I meet these requirements and accept the\",\n )}{\" \"}\n <a\n href={DEVELOPER_LINKS.developerAgreement}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-primary hover:underline\"\n >\n {tr(\n \"devportal.onboarding.developerAgreement\",\n \"Marketplace & Developer Agreement\",\n )}\n </a>{\" \"}\n {tr(\"devportal.onboarding.and\", \"and the\")}{\" \"}\n <a\n href={DEVELOPER_LINKS.acceptableUsePolicy}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-primary hover:underline\"\n >\n {tr(\n \"devportal.onboarding.acceptableUsePolicy\",\n \"Acceptable Use Policy\",\n )}\n </a>\n .\n </span>\n </label>\n {orgErrors.agreement && (\n <p className=\"mt-1.5 text-xs text-status-error-text\">\n {orgErrors.agreement}\n </p>\n )}\n </div>\n\n {orgSubmitError && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-text\"\n >\n {orgSubmitError}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleOrgSubmit()}\n disabled={creatingOrg}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-60 transition-colors\"\n >\n {creatingOrg ? (\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n ) : null}\n {tr(\"devportal.setup.createAndContinue\", \"Create & Continue\")}\n {!creatingOrg && <ChevronRight size={16} />}\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 2: First app ─────────────────────── */}\n {step === \"app\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <AppWindow size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\"devportal.setup.appTitle\", \"Create your first app\")}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.setup.appSubtitle\",\n \"Register the app you want to publish or integrate.\",\n )}\n </p>\n </div>\n </div>\n\n <div className=\"space-y-5\">\n <InputField\n label={tr(\"devportal.applicationName\", \"Application Name\")}\n value={appName}\n onChange={(e) => {\n setAppName(e.target.value);\n if (!appSlug || appSlug === generateSlug(appName)) {\n setAppSlug(generateSlug(e.target.value));\n }\n setAppErrors((p) => ({ ...p, name: undefined }));\n }}\n placeholder={tr(\n \"devportal.applicationNamePlaceholder\",\n \"My Awesome App\",\n )}\n required\n error={appErrors.name}\n />\n\n {/* Builder type picker */}\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">\n {tr(\"devportal.applicationType\", \"Application Type\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <SetupTypeCard\n icon={Box}\n title=\"App\"\n description=\"OAuth integration for the platform\"\n selected={builderType === \"app\"}\n onClick={() => {\n setBuilderType(\"app\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n <SetupTypeCard\n icon={Puzzle}\n title=\"Node\"\n description=\"Custom FluidGrids workflow node\"\n selected={builderType === \"node\"}\n onClick={() => {\n setBuilderType(\"node\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n <SetupTypeCard\n icon={GitFork}\n title=\"Workflow\"\n description=\"Reusable automation template\"\n selected={builderType === \"workflow\"}\n onClick={() => {\n setBuilderType(\"workflow\");\n setAppErrors((p) => ({ ...p, builderType: undefined }));\n }}\n />\n </div>\n {appErrors.builderType && (\n <p className=\"mt-1.5 text-xs text-status-error-text\">\n {appErrors.builderType}\n </p>\n )}\n </div>\n\n <SelectField\n label={tr(\"devportal.category\", \"Category\")}\n value={appCategory}\n onChange={(e) => {\n setAppCategory(e.target.value);\n setAppErrors((p) => ({ ...p, category: undefined }));\n }}\n options={[...APP_CATEGORY_OPTIONS]}\n placeholder={tr(\"devportal.selectCategory\", \"Select category…\")}\n required\n error={appErrors.category}\n />\n\n <TextareaField\n label={tr(\"devportal.shortDescription\", \"Short Description\")}\n value={appShortDesc}\n onChange={(e) => {\n setAppShortDesc(e.target.value);\n setAppErrors((p) => ({ ...p, shortDescription: undefined }));\n }}\n placeholder={tr(\n \"devportal.shortDescriptionPlaceholder\",\n \"A brief description of what your app does\",\n )}\n required\n error={appErrors.shortDescription}\n rows={2}\n hint={`${appShortDesc.length}/200`}\n />\n\n {appSubmitError && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-status-error-bg px-3 py-2 text-sm text-status-error-text\"\n >\n {appSubmitError}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={handleSkip}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.skipForNow\", \"Skip for now\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAppSubmit()}\n disabled={creatingApp}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-60 transition-colors\"\n >\n {creatingApp ? (\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n ) : null}\n {tr(\"devportal.setup.createApp\", \"Create App\")}\n {!creatingApp && <ChevronRight size={16} />}\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 3: Get ready for review ─────────── */}\n {step === \"submit\" && (\n <div className=\"rounded-xl border border-border-default bg-surface-raised p-8 shadow-sm\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <ListChecks size={20} />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr(\n \"devportal.onboarding.readyTitle\",\n \"Get it ready for review\",\n )}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n \"devportal.onboarding.readySubtitle\",\n \"Reviewers check every app before it is listed. Your app page walks you through what they need, then you submit it from there. It stays private until it is approved and you publish it.\",\n )}\n </p>\n </div>\n </div>\n\n <ul\n data-testid=\"devportal-setup-review-requirements\"\n className=\"space-y-2\"\n >\n {reviewRequirements.map((item) => (\n <li\n key={item.id}\n className=\"flex items-start gap-3 rounded-md bg-bg-sunken px-4 py-3 text-sm text-text-primary\"\n >\n <Check\n size={16}\n className=\"mt-0.5 shrink-0 text-text-secondary\"\n aria-hidden=\"true\"\n />\n {tr(item.key, item.text)}\n </li>\n ))}\n </ul>\n\n <div className=\"mt-8 flex items-center justify-between border-t border-border-default pt-6\">\n <button\n type=\"button\"\n onClick={() => setStep(\"done\")}\n className=\"text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.onboarding.submitLater\", \"Do this later\")}\n </button>\n <button\n type=\"button\"\n data-testid=\"devportal-setup-finish-app\"\n onClick={() =>\n createdAppId && handleNavigate(`/apps/${createdAppId}`)\n }\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr(\"devportal.onboarding.finishApp\", \"Finish and submit\")}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )}\n\n {/* ── Step 4: Done ─────────────────────────── */}\n {step === \"done\" && (\n <div className=\"text-center\">\n <div className=\"mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-action-primary-bg/10 text-primary\">\n <Rocket size={32} />\n </div>\n <h2 className=\"text-2xl font-bold text-text-primary\">\n {tr(\"devportal.setup.doneTitle\", \"You're all set!\")}\n </h2>\n <p className=\"mt-2 text-text-secondary\">\n {tr(\n \"devportal.setup.doneSubtitle\",\n \"Your organization and first app are ready. Here's what to do next.\",\n )}\n </p>\n\n <div className=\"mt-8 grid gap-3 sm:grid-cols-2\">\n {createdAppId && (\n <NextStepCard\n icon={<AppWindow size={20} />}\n title={tr(\"devportal.setup.viewApp\", \"View your app\")}\n description={tr(\n \"devportal.setup.viewAppDesc\",\n \"Configure OAuth, webhooks, and versioning\",\n )}\n onClick={() => handleNavigate(`/apps/${createdAppId}`)}\n primary\n />\n )}\n <NextStepCard\n icon={<Key size={20} />}\n title={tr(\"devportal.setup.generateApiKey\", \"Generate API key\")}\n description={tr(\n \"devportal.setup.generateApiKeyDesc\",\n \"Create credentials to start calling APIs\",\n )}\n onClick={() => handleNavigate(\"/api-keys/new\")}\n />\n <NextStepCard\n icon={<BookOpen size={20} />}\n title={tr(\"devportal.setup.browseDocs\", \"Browse docs\")}\n description={tr(\n \"devportal.setup.browseDocsDesc\",\n \"Learn about publishing, SDKs, and webhooks\",\n )}\n onClick={() => handleNavigate(\"/docs\")}\n />\n <NextStepCard\n icon={<Puzzle size={20} />}\n title={tr(\n \"devportal.onboarding.publishConnector\",\n \"Publish a connector\",\n )}\n description={tr(\n \"devportal.onboarding.publishConnectorDesc\",\n \"List an API-key integration in the Workspaces integrations catalog\",\n )}\n onClick={() =>\n window.open(\n DEVELOPER_LINKS.publishConnector,\n \"_blank\",\n \"noopener,noreferrer\",\n )\n }\n />\n {createdOrgId && (\n <NextStepCard\n icon={<Building2 size={20} />}\n title={tr(\"devportal.setup.inviteTeam\", \"Invite your team\")}\n description={tr(\n \"devportal.setup.inviteTeamDesc\",\n \"Add members to collaborate on your apps\",\n )}\n onClick={() =>\n handleNavigate(`/organizations/${createdOrgId}/members`)\n }\n />\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/\")}\n className=\"mt-8 inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n {tr(\"devportal.setup.goToDashboard\", \"Go to dashboard\")}\n <ArrowRight size={14} />\n </button>\n </div>\n )}\n </div>\n </main>\n );\n};\n\n// ─── Eligibility panel ─────────────────────────────────────────────────────\n\nconst EligibilityPanel: FC<{\n tr: (key: string, fallback: string) => string;\n}> = ({ tr }) => (\n <section\n aria-labelledby=\"devportal-setup-eligibility-title\"\n data-testid=\"devportal-setup-eligibility\"\n className=\"rounded-lg border border-border-subtle bg-bg-sunken p-4\"\n >\n <h3\n id=\"devportal-setup-eligibility-title\"\n className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\"\n >\n <ShieldCheck size={16} className=\"text-primary\" aria-hidden=\"true\" />\n {tr(\"devportal.onboarding.eligibilityTitle\", \"Who can register\")}\n </h3>\n <ul className=\"mt-2 list-disc space-y-1 pl-5 text-sm text-text-secondary\">\n {ELIGIBILITY_RULES.map((rule) => (\n <li key={rule.id}>{tr(rule.key, rule.text)}</li>\n ))}\n </ul>\n </section>\n);\n\n// ─── Setup type card ───────────────────────────────────────────────────────\n\nconst SetupTypeCard: FC<{\n icon: FC<{ size?: number; className?: string }>;\n title: string;\n description: string;\n selected: boolean;\n onClick: () => void;\n}> = ({ icon: Icon, title, description, selected, onClick }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n \"relative flex flex-col gap-2 rounded-xl border-2 p-4 text-left transition-all hover:border-primary/50 hover:shadow-sm\",\n selected\n ? \"border-primary bg-action-primary-bg/5 shadow-sm\"\n : \"border-border-subtle bg-bg-surface\",\n )}\n >\n {selected && (\n <span className=\"absolute top-3 right-3 flex h-4 w-4 items-center justify-center rounded-full bg-action-primary-bg text-action-primary-text\">\n <Check size={10} />\n </span>\n )}\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken\">\n <Icon size={18} className=\"text-text-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// ─── Next-step card ────────────────────────────────────────────────────────\n\nconst NextStepCard: FC<{\n icon: React.ReactNode;\n title: string;\n description: string;\n onClick: () => void;\n primary?: boolean;\n}> = ({ icon, title, description, onClick, primary }) => (\n <button\n type=\"button\"\n onClick={onClick}\n className={[\n \"flex items-start gap-4 rounded-xl border p-5 text-left transition-colors\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-default bg-surface-raised hover:bg-surface-hover\",\n ].join(\" \")}\n >\n <div\n className={[\n \"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg\",\n primary\n ? \"bg-action-primary-bg/20 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary\">{title}</p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>\n </div>\n <ChevronRight\n size={16}\n className=\"mt-0.5 flex-shrink-0 text-text-secondary\"\n />\n </button>\n);\n"],"mappings":"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"}
|
|
@@ -16,6 +16,11 @@ type DevportalContextValue = DevportalRootProps & {
|
|
|
16
16
|
activePhysicalOrgId: string | null;
|
|
17
17
|
setActivePhysicalOrgId: (id: string | null) => void;
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* True when the host opted into the FluidGrids variant. Safe outside a provider
|
|
21
|
+
* (standalone/tests): returns false instead of throwing.
|
|
22
|
+
*/
|
|
23
|
+
export declare const useIsFluidgridsVariant: () => boolean;
|
|
19
24
|
/**
|
|
20
25
|
* Hook to access Developer Portal context
|
|
21
26
|
*/
|
|
@@ -4,54 +4,55 @@ import { MutationCache as a, QueryClient as o, QueryClientProvider as s } from "
|
|
|
4
4
|
import { PermissionProvider as c } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
5
5
|
import { jsx as l } from "react/jsx-runtime";
|
|
6
6
|
// src/devportal/providers/DevportalProvider.tsx
|
|
7
|
-
var u = "devportal_active_org_v1", d = "devportal_active_physical_org_v1", f = e(null), p = () => {
|
|
7
|
+
var u = "devportal_active_org_v1", d = "devportal_active_physical_org_v1", f = e(null), p = () => t(f)?.variant === "fluidgrids", m = () => {
|
|
8
8
|
let e = t(f);
|
|
9
9
|
if (!e) throw Error("useDevportal must be used within DevportalProvider");
|
|
10
10
|
return e;
|
|
11
|
-
},
|
|
12
|
-
let [
|
|
11
|
+
}, h = ({ children: e, basePath: t, productId: p, variant: m, navigate: h, currentUser: g, tenantId: _, workspaceId: v, apiGatewayUrl: y, workspacesGatewayUrl: b, authToken: x, workspaceToken: S, getWorkspaceToken: C, registerNavItems: w, registerFooterItems: T, onApplicationSelect: E, onOrganizationSelect: D }) => {
|
|
12
|
+
let [O, k] = r(() => {
|
|
13
13
|
try {
|
|
14
14
|
return localStorage.getItem(u) ?? null;
|
|
15
15
|
} catch {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
}),
|
|
19
|
-
|
|
18
|
+
}), A = (e) => {
|
|
19
|
+
k(e);
|
|
20
20
|
try {
|
|
21
21
|
e ? localStorage.setItem(u, e) : localStorage.removeItem(u);
|
|
22
22
|
} catch {}
|
|
23
|
-
}, [
|
|
23
|
+
}, [j, M] = r(() => {
|
|
24
24
|
try {
|
|
25
25
|
return localStorage.getItem(d) ?? null;
|
|
26
26
|
} catch {
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
|
-
}),
|
|
29
|
+
}), N = {
|
|
30
30
|
basePath: t,
|
|
31
31
|
productId: p,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
variant: m,
|
|
33
|
+
navigate: h,
|
|
34
|
+
currentUser: g,
|
|
35
|
+
tenantId: _,
|
|
36
|
+
workspaceId: v,
|
|
37
|
+
apiGatewayUrl: y,
|
|
38
|
+
workspacesGatewayUrl: b,
|
|
39
|
+
authToken: x,
|
|
40
|
+
workspaceToken: S,
|
|
41
|
+
getWorkspaceToken: C,
|
|
42
|
+
registerNavItems: w,
|
|
43
|
+
registerFooterItems: T,
|
|
44
|
+
onApplicationSelect: E,
|
|
45
|
+
onOrganizationSelect: D,
|
|
46
|
+
activeOrgId: O,
|
|
47
|
+
setActiveOrgId: A,
|
|
48
|
+
activePhysicalOrgId: j,
|
|
48
49
|
setActivePhysicalOrgId: (e) => {
|
|
49
|
-
|
|
50
|
+
M(e);
|
|
50
51
|
try {
|
|
51
52
|
e ? localStorage.setItem(d, e) : localStorage.removeItem(d);
|
|
52
53
|
} catch {}
|
|
53
54
|
}
|
|
54
|
-
},
|
|
55
|
+
}, P = n(() => {
|
|
55
56
|
let e = new o({
|
|
56
57
|
mutationCache: new a({ onSuccess: () => {
|
|
57
58
|
e.invalidateQueries();
|
|
@@ -63,19 +64,19 @@ var u = "devportal_active_org_v1", d = "devportal_active_physical_org_v1", f = e
|
|
|
63
64
|
} }
|
|
64
65
|
});
|
|
65
66
|
return e;
|
|
66
|
-
}, []),
|
|
67
|
-
client:
|
|
67
|
+
}, []), F = !!t, I = /* @__PURE__ */ l(s, {
|
|
68
|
+
client: P,
|
|
68
69
|
children: /* @__PURE__ */ l(f.Provider, {
|
|
69
|
-
value:
|
|
70
|
-
children:
|
|
71
|
-
scopeId:
|
|
70
|
+
value: N,
|
|
71
|
+
children: F ? /* @__PURE__ */ l(c, {
|
|
72
|
+
scopeId: _ ?? null,
|
|
72
73
|
gateway: "global",
|
|
73
74
|
children: e
|
|
74
75
|
}) : e
|
|
75
76
|
})
|
|
76
77
|
});
|
|
77
|
-
return
|
|
78
|
+
return F ? I : /* @__PURE__ */ l(i, { children: I });
|
|
78
79
|
};
|
|
79
|
-
export {
|
|
80
|
+
export { h as DevportalProvider, m as useDevportal, p as useIsFluidgridsVariant };
|
|
80
81
|
|
|
81
82
|
//# sourceMappingURL=DevportalProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevportalProvider.js","names":["ORG_STORAGE_KEY","PHYSICAL_ORG_STORAGE_KEY","DevportalContext","createContext","
|
|
1
|
+
{"version":3,"file":"DevportalProvider.js","names":["ORG_STORAGE_KEY","PHYSICAL_ORG_STORAGE_KEY","DevportalContext","createContext","useIsFluidgridsVariant","useContext","useDevportal","context","DevportalProvider","activeOrgId","setActiveOrgIdState","useState","setActiveOrgId","id","activePhysicalOrgId","setActivePhysicalOrgIdState","contextValue","queryClient","useMemo","client","QueryClient","MutationCache","isIntegrated","basePath","content","QueryClientProvider","PermissionProvider","tenantId","children","BrowserRouter"],"sources":["../../../src/devportal/providers/DevportalProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useState } from \"react\";\nimport { BrowserRouter } from \"react-router\";\nimport {\n MutationCache,\n QueryClient,\n QueryClientProvider,\n} from \"@tanstack/react-query\";\nimport { PermissionProvider } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport type { DevportalRootProps } from \"../types\";\n\nconst ORG_STORAGE_KEY = \"devportal_active_org_v1\";\nconst PHYSICAL_ORG_STORAGE_KEY = \"devportal_active_physical_org_v1\";\n\n/**\n * Context value for Developer Portal microfrontend.\n * Extends root props with org-scoping state shared across all devportal pages.\n */\ntype DevportalContextValue = DevportalRootProps & {\n activeOrgId: string | null;\n setActiveOrgId: (id: string | null) => void;\n /**\n * The seller portal's currently-selected PHYSICAL-kind org. Kept separate\n * from activeOrgId (which the rest of devportal treats as DIGITAL) so\n * switching between the seller portal and the regular devportal never\n * mixes up which kind of org is \"active\" — see DeveloperOrgKind (BOFF-7285).\n */\n activePhysicalOrgId: string | null;\n setActivePhysicalOrgId: (id: string | null) => void;\n};\n\nconst DevportalContext = createContext<DevportalContextValue | null>(null);\n\n/**\n * True when the host opted into the FluidGrids variant. Safe outside a provider\n * (standalone/tests): returns false instead of throwing.\n */\nexport const useIsFluidgridsVariant = (): boolean =>\n useContext(DevportalContext)?.variant === \"fluidgrids\";\n\n/**\n * Hook to access Developer Portal context\n */\nexport const useDevportal = () => {\n const context = useContext(DevportalContext);\n if (!context) {\n throw new Error(\"useDevportal must be used within DevportalProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Developer Portal microfrontend\n * Sets up routing, context, and React Query client\n */\nexport const DevportalProvider: FC<PropsWithChildren<DevportalRootProps>> = ({\n children,\n basePath,\n productId,\n variant,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n workspacesGatewayUrl,\n authToken,\n workspaceToken,\n getWorkspaceToken,\n registerNavItems,\n registerFooterItems,\n onApplicationSelect,\n onOrganizationSelect,\n}) => {\n const [activeOrgId, setActiveOrgIdState] = useState<string | null>(() => {\n try {\n return localStorage.getItem(ORG_STORAGE_KEY) ?? null;\n } catch {\n return null;\n }\n });\n\n const setActiveOrgId = (id: string | null) => {\n setActiveOrgIdState(id);\n try {\n if (id) {\n localStorage.setItem(ORG_STORAGE_KEY, id);\n } else {\n localStorage.removeItem(ORG_STORAGE_KEY);\n }\n } catch {\n // ignore storage errors\n }\n };\n\n const [activePhysicalOrgId, setActivePhysicalOrgIdState] = useState<\n string | null\n >(() => {\n try {\n return localStorage.getItem(PHYSICAL_ORG_STORAGE_KEY) ?? null;\n } catch {\n return null;\n }\n });\n\n const setActivePhysicalOrgId = (id: string | null) => {\n setActivePhysicalOrgIdState(id);\n try {\n if (id) {\n localStorage.setItem(PHYSICAL_ORG_STORAGE_KEY, id);\n } else {\n localStorage.removeItem(PHYSICAL_ORG_STORAGE_KEY);\n }\n } catch {\n // ignore storage errors\n }\n };\n\n const contextValue: DevportalContextValue = {\n basePath,\n productId,\n variant,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n workspacesGatewayUrl,\n authToken,\n workspaceToken,\n getWorkspaceToken,\n registerNavItems,\n registerFooterItems,\n onApplicationSelect,\n onOrganizationSelect,\n activeOrgId,\n setActiveOrgId,\n activePhysicalOrgId,\n setActivePhysicalOrgId,\n };\n\n // Create QueryClient instance (memoized to avoid recreation on re-renders)\n const queryClient = useMemo(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n });\n return client;\n }, []);\n\n // PermissionProvider requires the shell's AuthProvider — only mount it in integrated mode.\n // In standalone/dev mode (MemoryRouter path), there's no AuthProvider, so we skip it\n // and pages will use a fallback that allows access (dev-only behaviour).\n const isIntegrated = !!basePath;\n\n // Wrap content with QueryClientProvider\n const content = (\n <QueryClientProvider client={queryClient}>\n <DevportalContext.Provider value={contextValue}>\n {isIntegrated ? (\n <PermissionProvider scopeId={tenantId ?? null} gateway=\"global\">\n {children}\n </PermissionProvider>\n ) : (\n children\n )}\n </DevportalContext.Provider>\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use BrowserRouter.\n if (isIntegrated) {\n return content;\n }\n\n // Standalone mode: create BrowserRouter for independent operation\n return <BrowserRouter>{content}</BrowserRouter>;\n};\n"],"mappings":";;;;;;AAWA,IAAMA,IAAkB,2BAClBC,IAA2B,oCAmB3BC,IAAmBC,EAA4C,IAAI,GAM5DC,UACXC,EAAWH,CAAgB,CAAC,EAAE,YAAY,cAK/BI,UAAqB;CAChC,IAAMC,IAAUF,EAAWH,CAAgB;CAC3C,IAAI,CAACK,GACH,MAAU,MAAM,oDAAoD;CAEtE,OAAOA;AACT,GAMaC,KAAgE,EAC3E,aACA,aACA,cACA,YACA,aACA,gBACA,aACA,gBACA,kBACA,yBACA,cACA,mBACA,sBACA,qBACA,wBACA,wBACA,8BACI;CACJ,IAAM,CAACC,GAAaC,KAAuBC,QAA8B;EACvE,IAAI;GACF,OAAO,aAAa,QAAQX,CAAe,KAAK;EAClD,QAAQ;GACN,OAAO;EACT;CACF,CAAC,GAEKY,KAAkB,MAAsB;EAC5C,EAAoBC,CAAE;EACtB,IAAI;GACF,AAAIA,IACF,aAAa,QAAQb,GAAiBa,CAAE,IAExC,aAAa,WAAWb,CAAe;EAE3C,QAAQ,CAER;CACF,GAEM,CAACc,GAAqBC,KAA+BJ,QAEnD;EACN,IAAI;GACF,OAAO,aAAa,QAAQV,CAAwB,KAAK;EAC3D,QAAQ;GACN,OAAO;EACT;CACF,CAAC,GAeKe,IAAsC;EAC1C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,yBAjC8B,MAAsB;GACpD,EAA4BH,CAAE;GAC9B,IAAI;IACF,AAAIA,IACF,aAAa,QAAQZ,GAA0BY,CAAE,IAEjD,aAAa,WAAWZ,CAAwB;GAEpD,QAAQ,CAER;EACF;CAuBA,GAGMgB,IAAcC,QAAc;EAChC,IAAMC,IAAS,IAAIC,EAAY;GAC7B,eAAe,IAAIC,EAAc,EAC/B,iBAAiB;IACf,EAAY,kBAAkB;GAChC,EACF,CAAC;GACD,gBAAgB,EACd,SAAS;IACP,OAAO;IACP,sBAAsB;IACtB,WAAW;GACb,EACF;EACF,CAAC;EACD,OAAOF;CACT,GAAG,CAAC,CAAC,GAKCG,IAAe,CAAC,CAACC,GAGjBC,IACJ,kBAACC,GAAD;EAAqB,QAAQR;EAC3B,UAAA,kBAACf,EAAiB,UAAlB;GAA2B,OAAOc;GAC/B,UAAAM,IACC,kBAACI,GAAD;IAAoB,SAASC,KAAY;IAAM,SAAQ;IACpD;GACiB,CAAA,IAEpBC;EAEuB,CAAA;CACR,CAAA;CAUvB,OALIN,IACKE,IAIF,kBAACK,GAAD,EAAA,UAAgBL,EAAuB,CAAA;AAChD"}
|
|
@@ -40,6 +40,12 @@ export interface DevportalRootProps {
|
|
|
40
40
|
* package; read it from this prop instead.
|
|
41
41
|
*/
|
|
42
42
|
productId?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Opt-in design variant. Only `fluidgrids-app` passes `"fluidgrids"`, which
|
|
45
|
+
* switches on the Navigation v2 developer-portal changes (BOFF-11862/11863).
|
|
46
|
+
* When unset the module looks and behaves exactly as it always has.
|
|
47
|
+
*/
|
|
48
|
+
variant?: "fluidgrids";
|
|
43
49
|
/**
|
|
44
50
|
* Tenant ID for scoping operations
|
|
45
51
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["WEBHOOK_EVENTS","AVAILABLE_SCOPES"],"sources":["../../../src/devportal/types/index.ts"],"sourcesContent":["/**\n * Developer Portal Types\n *\n * Core type definitions for the Developer Portal microfrontend\n */\n\nimport type { ReactNode } from \"react\";\n\n// =============================================================================\n// NAVIGATION TYPES\n// =============================================================================\n\n/**\n * Navigation item for shell integration\n */\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n children?: NavItem[];\n}\n\n/**\n * Footer item for shell integration\n */\nexport interface FooterItem {\n id: string;\n label: string;\n type: \"status\" | \"action\";\n order?: number;\n section: \"left\" | \"center\" | \"right\";\n}\n\n// =============================================================================\n// ROOT PROPS\n// =============================================================================\n\n/**\n * Props for the DevportalRoot component\n */\nexport interface DevportalRootProps {\n /**\n * Base path for the devportal microfrontend\n */\n basePath?: string;\n\n /**\n * Slug of the platform product hosting this devportal instance (e.g.\n * \"healthybowl\", \"vibecontrols\"). Optional — when a host shell doesn't\n * pass it, product-scoped behavior (like tagging a seller-portal physical\n * item's compatibleProducts) simply stays inert for that shell, matching\n * today's behavior. Never hardcode a specific product's slug in this\n * package; read it from this prop instead.\n */\n productId?: string;\n\n /**\n * Tenant ID for scoping operations\n */\n tenantId?: string;\n\n /**\n * Workspace ID (optional, for workspace-scoped features)\n */\n workspaceId?: string;\n\n /**\n * API Gateway URL for GraphQL requests (global gateway)\n */\n apiGatewayUrl?: string;\n\n /**\n * Workspace gateway URL for workspace-layer operations (e.g. wspace-files-svc uploads)\n * e.g. \"https://graphqlworkspaces.burdenoff.com\" or \"http://localhost:4003\"\n */\n workspacesGatewayUrl?: string;\n\n /**\n * Authentication token for API requests\n */\n authToken?: string;\n\n /**\n * Workspace authentication token for workspace-gateway requests (X-Workspace-Authorization).\n * May be a cached/stale value — use getWorkspaceToken() for upload flows that need a fresh token.\n */\n workspaceToken?: string;\n\n /**\n * Async getter that always returns a valid (non-expired) workspace token.\n * Call this instead of reading workspaceToken directly for workspace-layer mutations.\n */\n getWorkspaceToken?: () => Promise<string | null>;\n\n /**\n * Navigation function provided by the shell\n */\n navigate?: (path: string) => void;\n\n /**\n * Current user information\n */\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n };\n\n /**\n * Optional function to register navigation items with the shell\n */\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n\n /**\n * Optional function to register footer items with the shell\n */\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n\n /**\n * Optional callback when an application is selected\n */\n onApplicationSelect?: (app: Application) => void;\n\n /**\n * Optional callback when an organization is selected\n */\n onOrganizationSelect?: (org: DeveloperOrganization) => void;\n}\n\n// =============================================================================\n// APPLICATION TYPES\n// =============================================================================\n\nexport type AppType =\n | \"WEB_APP\"\n | \"MOBILE_APP\"\n | \"DESKTOP_APP\"\n | \"CLI_TOOL\"\n | \"API_INTEGRATION\"\n | \"WORKFLOW\"\n | \"AUTOMATION\"\n | \"WIDGET\"\n | \"BOT\"\n | \"SERVICE\"\n | \"INTEGRATION\"\n | \"ANALYST\"\n | \"ASSISTANT\"\n | \"SPECIALIST\"\n | \"NODE\"\n | \"VIBEMODULE\"\n | \"CONSOLE\"\n | \"DASHBOARD\"\n | \"PARSER\"\n | \"DATASINK\";\n\nexport type AppStatus =\n | \"DRAFT\"\n | \"SUBMITTED\"\n | \"IN_REVIEW\"\n | \"APPROVED\"\n | \"PUBLISHED\"\n | \"UNLISTED\"\n | \"REJECTED\"\n | \"SUSPENDED\"\n | \"DEPRECATED\"\n | \"ACTIVE\"\n | \"INACTIVE\"\n | \"REVOKED\";\n\nexport type AppVisibility = \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\" | \"INTERNAL\";\n\nexport type AppSubmissionReviewStatus =\n \"PENDING\" | \"IN_PROGRESS\" | \"APPROVED\" | \"REJECTED\" | \"CHANGES_REQUESTED\";\n\nexport interface Application {\n id: string;\n name: string;\n slug: string;\n shortDescription?: string;\n description?: string;\n appType: AppType;\n status: AppStatus;\n visibility: AppVisibility;\n tenantId: string;\n developerId: string;\n developerOrganizationId?: string;\n developerOrganization?: DeveloperOrganization;\n category?: string;\n tags: string[];\n keywords: string[];\n iconUrl?: string;\n bannerUrl?: string;\n videoUrl?: string;\n screenshots?: Record<string, unknown>;\n websiteUrl?: string;\n documentationUrl?: string;\n supportUrl?: string;\n privacyPolicyUrl?: string;\n termsOfServiceUrl?: string;\n githubUrl?: string;\n pricingType?: string;\n pricingDetails?: Record<string, unknown>;\n installCount: number;\n activeInstallCount: number;\n averageRating?: number;\n reviewCount: number;\n submittedAt?: string;\n publishedAt?: string;\n lastReviewedAt?: string;\n reviewStatus: AppSubmissionReviewStatus;\n reviewNotes?: string;\n reviewedBy?: string;\n isFeatured: boolean;\n isVerified: boolean;\n requiresApproval: boolean;\n manifest?: Record<string, unknown>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n appId?: string;\n oauthAuthUrl?: string;\n oauthRevokeUrl?: string;\n createdAt: string;\n updatedAt: string;\n versions?: ApplicationVersion[];\n oauthClients?: OAuthClient[];\n apiKeys?: ApiKey[];\n webhooks?: Webhook[];\n scopes?: ApplicationScope[];\n}\n\nexport interface ApplicationVersion {\n id: string;\n applicationId: string;\n version: string;\n releaseNotes?: string;\n changelog?: string;\n status: string;\n isLatest: boolean;\n isStable: boolean;\n packageUrl?: string;\n packageSize?: string;\n packageHash?: string;\n manifest?: Record<string, unknown>;\n minPlatformVersion?: string;\n maxPlatformVersion?: string;\n requiredScopes: string[];\n publishedAt?: string;\n deprecatedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ApplicationScope {\n id: string;\n applicationId: string;\n scope: string;\n description?: string;\n isRequired: boolean;\n createdAt: string;\n}\n\n// =============================================================================\n// OAUTH TYPES\n// =============================================================================\n\nexport type OAuthClientType = \"PUBLIC\" | \"CONFIDENTIAL\";\n\nexport type OAuthGrantType =\n | \"AUTHORIZATION_CODE\"\n | \"REFRESH_TOKEN\"\n | \"CLIENT_CREDENTIALS\"\n | \"IMPLICIT\"\n | \"PASSWORD\";\n\nexport interface OAuthClient {\n id: string;\n applicationId: string;\n name: string;\n clientId: string;\n clientSecret: string;\n clientType: OAuthClientType;\n redirectUris: string[];\n allowedGrantTypes: OAuthGrantType[];\n allowedScopes: string[];\n accessTokenLifetime: number;\n refreshTokenLifetime: number;\n isActive: boolean;\n requirePKCE: boolean;\n requireConsent: boolean;\n metadata?: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OAuthToken {\n id: string;\n clientId: string;\n userId: string;\n tenantId: string;\n workspaceId?: string;\n accessToken: string;\n refreshToken?: string;\n tokenType: string;\n scopes: string[];\n expiresAt: string;\n refreshExpiresAt?: string;\n isRevoked: boolean;\n revokedAt?: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n// =============================================================================\n// API KEY TYPES\n// =============================================================================\n\nexport type ApiKeyType = \"PUBLISHABLE\" | \"SECRET\" | \"RESTRICTED\";\n\nexport interface ApiKey {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n keyId: string;\n keyHash?: string; // Hash is not always returned by API for security\n keyPrefix: string;\n keyType: ApiKeyType;\n scopes: string[];\n permissions?: Record<string, unknown>;\n rateLimit?: number;\n quotaLimit?: number;\n quotaUsed: number;\n quotaResetAt?: string;\n allowedIps: string[];\n allowedDomains: string[];\n environment?: string;\n isActive: boolean;\n isRevoked: boolean;\n createdAt: string;\n lastUsedAt?: string;\n expiresAt?: string;\n revokedAt?: string;\n}\n\nexport interface ApiKeyPayload {\n apiKey: ApiKey;\n plainTextKey: string;\n}\n\n// =============================================================================\n// WEBHOOK TYPES\n// =============================================================================\n\nexport type WebhookStatus = \"ACTIVE\" | \"PAUSED\" | \"FAILED\" | \"DISABLED\";\n\nexport type WebhookDeliveryStatus =\n \"PENDING\" | \"DELIVERING\" | \"DELIVERED\" | \"FAILED\" | \"RETRYING\";\n\nexport interface Webhook {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n url: string;\n secret?: string;\n events: string[];\n filters?: Record<string, unknown>;\n retryEnabled: boolean;\n maxRetries: number;\n retryDelay: number;\n status: WebhookStatus;\n isActive: boolean;\n successCount: number;\n failureCount: number;\n lastTriggeredAt?: string;\n lastSuccessAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n createdAt: string;\n updatedAt: string;\n deliveries?: WebhookDelivery[];\n}\n\nexport interface WebhookDelivery {\n id: string;\n webhookId: string;\n eventType: string;\n eventId: string;\n payload: Record<string, unknown>;\n attemptNumber: number;\n status: WebhookDeliveryStatus;\n requestHeaders?: Record<string, unknown>;\n requestBody?: Record<string, unknown>;\n responseStatus?: number;\n responseHeaders?: Record<string, unknown>;\n responseBody?: string;\n deliveredAt?: string;\n failedAt?: string;\n nextRetryAt?: string;\n errorMessage?: string;\n errorCode?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode?: number;\n response?: string;\n error?: string;\n}\n\n// =============================================================================\n// DEVELOPER ORGANIZATION TYPES\n// =============================================================================\n\nexport type DeveloperOrgRole = \"OWNER\" | \"ADMIN\" | \"DEVELOPER\" | \"VIEWER\";\n\nexport type DeveloperOrgTier =\n \"FREE\" | \"DEVELOPER\" | \"PROFESSIONAL\" | \"ENTERPRISE\";\n\nexport type PartnerTier = \"REGISTERED\" | \"SELECT\" | \"PREMIER\";\n\nexport type InvitationStatus =\n \"PENDING\" | \"ACCEPTED\" | \"REJECTED\" | \"EXPIRED\" | \"CANCELLED\";\n\nexport interface DeveloperOrganization {\n id: string;\n name: string;\n slug: string;\n kind?: \"DIGITAL\" | \"PHYSICAL\";\n description?: string;\n logoUrl?: string;\n bannerUrl?: string;\n websiteUrl?: string;\n contactEmail?: string;\n supportEmail?: string;\n tier: DeveloperOrgTier;\n partnerTier?: PartnerTier;\n partnerSince?: string;\n isVerified: boolean;\n verifiedAt?: string;\n verificationMethod?: string;\n verificationRequestedAt?: string;\n verificationStatus?: \"UNVERIFIED\" | \"PENDING\" | \"VERIFIED\" | \"REJECTED\";\n verificationRequestNotes?: string;\n verificationEvidenceUrl?: string;\n verificationDomain?: string;\n verificationRequestedBy?: string;\n verificationReviewedAt?: string;\n verificationReviewedBy?: string;\n verificationReviewNotes?: string;\n verificationRejectedReason?: string;\n isCertified: boolean;\n certifiedAt?: string;\n certificationExpiry?: string;\n totalApps: number;\n publishedApps: number;\n totalInstalls: number;\n activeInstalls: number;\n totalApiCalls: string;\n averageRating?: number;\n totalRevenue: number;\n totalPlatformFees: number;\n totalPayouts: number;\n pendingBalance: number;\n lifetimeRevenue: number;\n platformFeeRate: number;\n apiCallsLimit: number;\n storageLimit: string;\n apiCallsUsed: string;\n storageUsed: string;\n quotaResetAt?: string;\n taxId?: string;\n taxCountry?: string;\n taxFormType?: string;\n taxFormVerified: boolean;\n legalEntityName?: string;\n legalEntityType?: string;\n legalAddress?: Record<string, unknown>;\n settings?: Record<string, unknown>;\n billingAccountId?: string;\n billingLinkedAt?: string;\n pickupPostalCode?: string;\n createdAt: string;\n updatedAt: string;\n members?: DeveloperOrgMember[];\n invitations?: DeveloperOrgInvitation[];\n applications?: Application[];\n}\n\nexport interface DeveloperOrgMember {\n id: string;\n organizationId: string;\n userId: string;\n email?: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy?: string;\n invitationId?: string;\n isActive: boolean;\n isSuspended: boolean;\n suspendedAt?: string;\n suspendedReason?: string;\n joinedAt: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DeveloperOrgInvitation {\n id: string;\n organizationId: string;\n invitedUserId?: string;\n invitedEmail: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy: string;\n message?: string;\n token: string;\n status: InvitationStatus;\n expiresAt: string;\n respondedAt?: string;\n memberId?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// ANALYTICS TYPES (MOCKED)\n// =============================================================================\n\nexport interface ApplicationAnalytics {\n id: string;\n applicationId: string;\n date: string;\n period: string;\n activeUsers: number;\n newUsers: number;\n apiCalls: number;\n errors: number;\n avgResponseTime?: number;\n sessions: number;\n avgSessionDuration?: number;\n createdAt: string;\n}\n\nexport interface DeveloperOrgRevenueAnalytics {\n id: string;\n organizationId: string;\n period: string;\n periodStart: string;\n periodEnd: string;\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n salesCount: number;\n refundsCount: number;\n refundsAmount: number;\n newSubscribers: number;\n churnedSubscribers: number;\n activeSubscribers: number;\n avgRevenuePerUser: number;\n topApps?: Array<{ appId: string; revenue: number }>;\n createdAt: string;\n}\n\n// =============================================================================\n// MONETIZATION TYPES (MOCKED)\n// =============================================================================\n\nexport type DevPortalPricingModel =\n | \"FREE\"\n | \"FREEMIUM\"\n | \"SUBSCRIPTION\"\n | \"ONE_TIME\"\n | \"USAGE_BASED\"\n | \"ENTERPRISE\";\n\nexport type SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\nexport type PayoutStatus =\n \"PENDING\" | \"PROCESSING\" | \"COMPLETED\" | \"FAILED\" | \"CANCELLED\";\n\nexport interface DeveloperOrgSale {\n id: string;\n organizationId: string;\n applicationId: string;\n buyerUserId: string;\n buyerTenantId?: string;\n installationScope: string;\n pricingModel: DevPortalPricingModel;\n planName?: string;\n grossAmount: number;\n platformFeeRate: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n taxAmount?: number;\n discountAmount?: number;\n status: SaleStatus;\n createdAt: string;\n}\n\nexport interface DeveloperOrgPayout {\n id: string;\n organizationId: string;\n periodStart: string;\n periodEnd: string;\n grossAmount: number;\n platformFees: number;\n netAmount: number;\n currency: string;\n status: PayoutStatus;\n processedAt?: string;\n failedAt?: string;\n failureReason?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// APP INSTALLATION TYPES\n// =============================================================================\n\nexport type AppInstallationStatus =\n \"ACTIVE\" | \"SUSPENDED\" | \"TRIAL\" | \"EXPIRED\" | \"UNINSTALLED\";\n\nexport interface AppInstallation {\n id: string;\n applicationId: string;\n userId: string;\n tenantId?: string;\n workspaceId?: string;\n organizationId?: string;\n scope: string;\n installedVersion: string;\n status: AppInstallationStatus;\n grantedScopes: string[];\n settings?: Record<string, unknown>;\n installedAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// REVIEW TYPES\n// =============================================================================\n\nexport type ReviewStatus =\n \"PENDING\" | \"PUBLISHED\" | \"HIDDEN\" | \"FLAGGED\" | \"REMOVED\";\n\nexport interface AppReview {\n id: string;\n applicationId: string;\n userId: string;\n rating: number;\n title?: string;\n body?: string;\n status: ReviewStatus;\n isFeatured: boolean;\n helpfulCount: number;\n notHelpfulCount: number;\n developerResponse?: string;\n developerResponseAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// DELEGATION TYPES\n// =============================================================================\n\nexport type DelegationStatus = \"ACTIVE\" | \"REVOKED\" | \"EXPIRED\";\n\nexport interface AppUserDelegation {\n id: string;\n appId: string;\n app?: {\n id: string;\n name: string;\n description?: string;\n type: string;\n status: string;\n };\n userId: string;\n scopes: string[];\n workspaceIds: string[];\n status: DelegationStatus;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string;\n revokedAt?: string;\n revokedBy?: string;\n}\n\nexport interface GrantDelegationInput {\n appId: string;\n userId?: string;\n workspaceIds: string[];\n scopes: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUTH APP TYPES (from auth-svc layer)\n// =============================================================================\n\nexport type AuthAppType =\n | \"ANALYST\"\n | \"API_INTEGRATION\"\n | \"ASSISTANT\"\n | \"AUTOMATION\"\n | \"BOT\"\n | \"CLI_TOOL\"\n | \"DESKTOP_APP\"\n | \"INTEGRATION\"\n | \"MOBILE_APP\"\n | \"SERVICE\"\n | \"SPECIALIST\"\n | \"WEB_APP\"\n | \"WIDGET\"\n | \"WORKFLOW\";\nexport type AuthAppStatus = \"ACTIVE\" | \"INACTIVE\" | \"REVOKED\";\n\nexport interface AuthApp {\n id: string;\n name: string;\n description?: string;\n clientId: string;\n type: AuthAppType;\n status: AuthAppStatus;\n scopes: string[];\n redirectUris: string[];\n ownerUserId: string;\n tenantId: string;\n delegations?: AppUserDelegation[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AuthAppCreateResponse {\n app: AuthApp;\n clientSecret: string;\n}\n\nexport interface CreateAuthAppInput {\n name: string;\n description?: string;\n type: AuthAppType;\n scopes: string[];\n redirectUris?: string[];\n tenantId?: string;\n}\n\nexport interface UpdateAuthAppInput {\n name?: string;\n description?: string;\n scopes?: string[];\n redirectUris?: string[];\n}\n\n// =============================================================================\n// AUTH API KEY TYPES (from auth-svc layer)\n// =============================================================================\n\nexport interface AuthApiKey {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string;\n createdAt: string;\n expiresAt?: string;\n lastUsedAt?: string;\n}\n\nexport interface AuthApiKeyResult {\n apiKey: string;\n apiSecret: string;\n keyId: string;\n}\n\nexport interface AuthApiKeyRotateResult {\n apiSecret: string;\n}\n\nexport interface GenerateAuthApiKeyInput {\n appId: string;\n name: string;\n scope: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUDIT LOG TYPES\n// =============================================================================\n\nexport interface AuditLog {\n id: string;\n actorId: string;\n actorType: string;\n tenantId: string;\n action: string;\n resource: string;\n resourceId?: string;\n changes?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// VIEW TYPES\n// =============================================================================\n\nexport type DevportalView =\n | \"dashboard\"\n | \"organizations\"\n | \"apps\"\n | \"oauth\"\n | \"api-keys\"\n | \"webhooks\"\n | \"delegations\"\n | \"analytics\"\n | \"revenue\"\n | \"docs\"\n | \"profile\"\n | \"settings\";\n\n// =============================================================================\n// FILTER TYPES\n// =============================================================================\n\nexport interface ApplicationFilter {\n search?: string;\n status?: AppStatus;\n visibility?: AppVisibility;\n appType?: AppType;\n organizationId?: string;\n isFeatured?: boolean;\n}\n\nexport interface WebhookFilter {\n status?: WebhookStatus;\n event?: string;\n}\n\n// =============================================================================\n// WEBHOOK EVENT TYPES\n// =============================================================================\n\nexport const WEBHOOK_EVENTS = [\n // Application events\n \"application.created\",\n \"application.updated\",\n \"application.deleted\",\n \"application.published\",\n \"application.unpublished\",\n \"application.suspended\",\n // Installation events\n \"installation.created\",\n \"installation.updated\",\n \"installation.deleted\",\n // User events\n \"user.created\",\n \"user.updated\",\n \"user.deleted\",\n // Workspace events\n \"workspace.created\",\n \"workspace.updated\",\n \"workspace.deleted\",\n // Project events\n \"project.created\",\n \"project.updated\",\n \"project.deleted\",\n // Task events\n \"task.created\",\n \"task.updated\",\n \"task.deleted\",\n \"task.completed\",\n // Comment events\n \"comment.created\",\n \"comment.updated\",\n \"comment.deleted\",\n // File events\n \"file.uploaded\",\n \"file.deleted\",\n // Member events\n \"member.added\",\n \"member.removed\",\n \"member.role_changed\",\n] as const;\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];\n\n// =============================================================================\n// SCOPE DEFINITIONS\n// =============================================================================\n\nexport const AVAILABLE_SCOPES = [\n // User scopes\n {\n scope: \"user:read\",\n description: \"Read user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:write\",\n description: \"Modify user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:email\",\n description: \"Access user email address\",\n category: \"User\",\n },\n // Workspace scopes\n {\n scope: \"workspace:read\",\n description: \"Read workspace information\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:write\",\n description: \"Create and modify workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:delete\",\n description: \"Delete workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:members\",\n description: \"Manage workspace members\",\n category: \"Workspace\",\n },\n // Project scopes\n {\n scope: \"project:read\",\n description: \"Read project information\",\n category: \"Project\",\n },\n {\n scope: \"project:write\",\n description: \"Create and modify projects\",\n category: \"Project\",\n },\n {\n scope: \"project:delete\",\n description: \"Delete projects\",\n category: \"Project\",\n },\n // Task scopes\n { scope: \"task:read\", description: \"Read tasks\", category: \"Task\" },\n {\n scope: \"task:write\",\n description: \"Create and modify tasks\",\n category: \"Task\",\n },\n { scope: \"task:delete\", description: \"Delete tasks\", category: \"Task\" },\n // File scopes\n { scope: \"file:read\", description: \"Read files\", category: \"File\" },\n {\n scope: \"file:write\",\n description: \"Upload and modify files\",\n category: \"File\",\n },\n { scope: \"file:delete\", description: \"Delete files\", category: \"File\" },\n // Comment scopes\n { scope: \"comment:read\", description: \"Read comments\", category: \"Comment\" },\n {\n scope: \"comment:write\",\n description: \"Create and modify comments\",\n category: \"Comment\",\n },\n {\n scope: \"comment:delete\",\n description: \"Delete comments\",\n category: \"Comment\",\n },\n // Offline access\n {\n scope: \"offline_access\",\n description: \"Access data while user is offline\",\n category: \"Access\",\n },\n] as const;\n\nexport type AvailableScope = (typeof AVAILABLE_SCOPES)[number][\"scope\"];\n"],"mappings":";AAg2BA,IAAaA,IAAiB,whBAwC9B,GAQaC,IAAmB;CAE9B;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;CAAO;CAClE;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;CAAO;CAEtE;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;CAAO;CAClE;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;CAAO;CAEtE;EAAE,OAAO;EAAgB,aAAa;EAAiB,UAAU;CAAU;CAC3E;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;AACF"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["WEBHOOK_EVENTS","AVAILABLE_SCOPES"],"sources":["../../../src/devportal/types/index.ts"],"sourcesContent":["/**\n * Developer Portal Types\n *\n * Core type definitions for the Developer Portal microfrontend\n */\n\nimport type { ReactNode } from \"react\";\n\n// =============================================================================\n// NAVIGATION TYPES\n// =============================================================================\n\n/**\n * Navigation item for shell integration\n */\nexport interface NavItem {\n id: string;\n label: string;\n path: string;\n icon?: ReactNode;\n badge?: number;\n order?: number;\n group?: string;\n visible?: boolean;\n children?: NavItem[];\n}\n\n/**\n * Footer item for shell integration\n */\nexport interface FooterItem {\n id: string;\n label: string;\n type: \"status\" | \"action\";\n order?: number;\n section: \"left\" | \"center\" | \"right\";\n}\n\n// =============================================================================\n// ROOT PROPS\n// =============================================================================\n\n/**\n * Props for the DevportalRoot component\n */\nexport interface DevportalRootProps {\n /**\n * Base path for the devportal microfrontend\n */\n basePath?: string;\n\n /**\n * Slug of the platform product hosting this devportal instance (e.g.\n * \"healthybowl\", \"vibecontrols\"). Optional — when a host shell doesn't\n * pass it, product-scoped behavior (like tagging a seller-portal physical\n * item's compatibleProducts) simply stays inert for that shell, matching\n * today's behavior. Never hardcode a specific product's slug in this\n * package; read it from this prop instead.\n */\n productId?: string;\n\n /**\n * Opt-in design variant. Only `fluidgrids-app` passes `\"fluidgrids\"`, which\n * switches on the Navigation v2 developer-portal changes (BOFF-11862/11863).\n * When unset the module looks and behaves exactly as it always has.\n */\n variant?: \"fluidgrids\";\n\n /**\n * Tenant ID for scoping operations\n */\n tenantId?: string;\n\n /**\n * Workspace ID (optional, for workspace-scoped features)\n */\n workspaceId?: string;\n\n /**\n * API Gateway URL for GraphQL requests (global gateway)\n */\n apiGatewayUrl?: string;\n\n /**\n * Workspace gateway URL for workspace-layer operations (e.g. wspace-files-svc uploads)\n * e.g. \"https://graphqlworkspaces.burdenoff.com\" or \"http://localhost:4003\"\n */\n workspacesGatewayUrl?: string;\n\n /**\n * Authentication token for API requests\n */\n authToken?: string;\n\n /**\n * Workspace authentication token for workspace-gateway requests (X-Workspace-Authorization).\n * May be a cached/stale value — use getWorkspaceToken() for upload flows that need a fresh token.\n */\n workspaceToken?: string;\n\n /**\n * Async getter that always returns a valid (non-expired) workspace token.\n * Call this instead of reading workspaceToken directly for workspace-layer mutations.\n */\n getWorkspaceToken?: () => Promise<string | null>;\n\n /**\n * Navigation function provided by the shell\n */\n navigate?: (path: string) => void;\n\n /**\n * Current user information\n */\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n };\n\n /**\n * Optional function to register navigation items with the shell\n */\n registerNavItems?: (items: NavItem | NavItem[]) => () => void;\n\n /**\n * Optional function to register footer items with the shell\n */\n registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;\n\n /**\n * Optional callback when an application is selected\n */\n onApplicationSelect?: (app: Application) => void;\n\n /**\n * Optional callback when an organization is selected\n */\n onOrganizationSelect?: (org: DeveloperOrganization) => void;\n}\n\n// =============================================================================\n// APPLICATION TYPES\n// =============================================================================\n\nexport type AppType =\n | \"WEB_APP\"\n | \"MOBILE_APP\"\n | \"DESKTOP_APP\"\n | \"CLI_TOOL\"\n | \"API_INTEGRATION\"\n | \"WORKFLOW\"\n | \"AUTOMATION\"\n | \"WIDGET\"\n | \"BOT\"\n | \"SERVICE\"\n | \"INTEGRATION\"\n | \"ANALYST\"\n | \"ASSISTANT\"\n | \"SPECIALIST\"\n | \"NODE\"\n | \"VIBEMODULE\"\n | \"CONSOLE\"\n | \"DASHBOARD\"\n | \"PARSER\"\n | \"DATASINK\";\n\nexport type AppStatus =\n | \"DRAFT\"\n | \"SUBMITTED\"\n | \"IN_REVIEW\"\n | \"APPROVED\"\n | \"PUBLISHED\"\n | \"UNLISTED\"\n | \"REJECTED\"\n | \"SUSPENDED\"\n | \"DEPRECATED\"\n | \"ACTIVE\"\n | \"INACTIVE\"\n | \"REVOKED\";\n\nexport type AppVisibility = \"PUBLIC\" | \"PRIVATE\" | \"UNLISTED\" | \"INTERNAL\";\n\nexport type AppSubmissionReviewStatus =\n \"PENDING\" | \"IN_PROGRESS\" | \"APPROVED\" | \"REJECTED\" | \"CHANGES_REQUESTED\";\n\nexport interface Application {\n id: string;\n name: string;\n slug: string;\n shortDescription?: string;\n description?: string;\n appType: AppType;\n status: AppStatus;\n visibility: AppVisibility;\n tenantId: string;\n developerId: string;\n developerOrganizationId?: string;\n developerOrganization?: DeveloperOrganization;\n category?: string;\n tags: string[];\n keywords: string[];\n iconUrl?: string;\n bannerUrl?: string;\n videoUrl?: string;\n screenshots?: Record<string, unknown>;\n websiteUrl?: string;\n documentationUrl?: string;\n supportUrl?: string;\n privacyPolicyUrl?: string;\n termsOfServiceUrl?: string;\n githubUrl?: string;\n pricingType?: string;\n pricingDetails?: Record<string, unknown>;\n installCount: number;\n activeInstallCount: number;\n averageRating?: number;\n reviewCount: number;\n submittedAt?: string;\n publishedAt?: string;\n lastReviewedAt?: string;\n reviewStatus: AppSubmissionReviewStatus;\n reviewNotes?: string;\n reviewedBy?: string;\n isFeatured: boolean;\n isVerified: boolean;\n requiresApproval: boolean;\n manifest?: Record<string, unknown>;\n config?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n appId?: string;\n oauthAuthUrl?: string;\n oauthRevokeUrl?: string;\n createdAt: string;\n updatedAt: string;\n versions?: ApplicationVersion[];\n oauthClients?: OAuthClient[];\n apiKeys?: ApiKey[];\n webhooks?: Webhook[];\n scopes?: ApplicationScope[];\n}\n\nexport interface ApplicationVersion {\n id: string;\n applicationId: string;\n version: string;\n releaseNotes?: string;\n changelog?: string;\n status: string;\n isLatest: boolean;\n isStable: boolean;\n packageUrl?: string;\n packageSize?: string;\n packageHash?: string;\n manifest?: Record<string, unknown>;\n minPlatformVersion?: string;\n maxPlatformVersion?: string;\n requiredScopes: string[];\n publishedAt?: string;\n deprecatedAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ApplicationScope {\n id: string;\n applicationId: string;\n scope: string;\n description?: string;\n isRequired: boolean;\n createdAt: string;\n}\n\n// =============================================================================\n// OAUTH TYPES\n// =============================================================================\n\nexport type OAuthClientType = \"PUBLIC\" | \"CONFIDENTIAL\";\n\nexport type OAuthGrantType =\n | \"AUTHORIZATION_CODE\"\n | \"REFRESH_TOKEN\"\n | \"CLIENT_CREDENTIALS\"\n | \"IMPLICIT\"\n | \"PASSWORD\";\n\nexport interface OAuthClient {\n id: string;\n applicationId: string;\n name: string;\n clientId: string;\n clientSecret: string;\n clientType: OAuthClientType;\n redirectUris: string[];\n allowedGrantTypes: OAuthGrantType[];\n allowedScopes: string[];\n accessTokenLifetime: number;\n refreshTokenLifetime: number;\n isActive: boolean;\n requirePKCE: boolean;\n requireConsent: boolean;\n metadata?: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface OAuthToken {\n id: string;\n clientId: string;\n userId: string;\n tenantId: string;\n workspaceId?: string;\n accessToken: string;\n refreshToken?: string;\n tokenType: string;\n scopes: string[];\n expiresAt: string;\n refreshExpiresAt?: string;\n isRevoked: boolean;\n revokedAt?: string;\n createdAt: string;\n lastUsedAt?: string;\n}\n\n// =============================================================================\n// API KEY TYPES\n// =============================================================================\n\nexport type ApiKeyType = \"PUBLISHABLE\" | \"SECRET\" | \"RESTRICTED\";\n\nexport interface ApiKey {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n keyId: string;\n keyHash?: string; // Hash is not always returned by API for security\n keyPrefix: string;\n keyType: ApiKeyType;\n scopes: string[];\n permissions?: Record<string, unknown>;\n rateLimit?: number;\n quotaLimit?: number;\n quotaUsed: number;\n quotaResetAt?: string;\n allowedIps: string[];\n allowedDomains: string[];\n environment?: string;\n isActive: boolean;\n isRevoked: boolean;\n createdAt: string;\n lastUsedAt?: string;\n expiresAt?: string;\n revokedAt?: string;\n}\n\nexport interface ApiKeyPayload {\n apiKey: ApiKey;\n plainTextKey: string;\n}\n\n// =============================================================================\n// WEBHOOK TYPES\n// =============================================================================\n\nexport type WebhookStatus = \"ACTIVE\" | \"PAUSED\" | \"FAILED\" | \"DISABLED\";\n\nexport type WebhookDeliveryStatus =\n \"PENDING\" | \"DELIVERING\" | \"DELIVERED\" | \"FAILED\" | \"RETRYING\";\n\nexport interface Webhook {\n id: string;\n applicationId: string;\n name: string;\n description?: string;\n url: string;\n secret?: string;\n events: string[];\n filters?: Record<string, unknown>;\n retryEnabled: boolean;\n maxRetries: number;\n retryDelay: number;\n status: WebhookStatus;\n isActive: boolean;\n successCount: number;\n failureCount: number;\n lastTriggeredAt?: string;\n lastSuccessAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n createdAt: string;\n updatedAt: string;\n deliveries?: WebhookDelivery[];\n}\n\nexport interface WebhookDelivery {\n id: string;\n webhookId: string;\n eventType: string;\n eventId: string;\n payload: Record<string, unknown>;\n attemptNumber: number;\n status: WebhookDeliveryStatus;\n requestHeaders?: Record<string, unknown>;\n requestBody?: Record<string, unknown>;\n responseStatus?: number;\n responseHeaders?: Record<string, unknown>;\n responseBody?: string;\n deliveredAt?: string;\n failedAt?: string;\n nextRetryAt?: string;\n errorMessage?: string;\n errorCode?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode?: number;\n response?: string;\n error?: string;\n}\n\n// =============================================================================\n// DEVELOPER ORGANIZATION TYPES\n// =============================================================================\n\nexport type DeveloperOrgRole = \"OWNER\" | \"ADMIN\" | \"DEVELOPER\" | \"VIEWER\";\n\nexport type DeveloperOrgTier =\n \"FREE\" | \"DEVELOPER\" | \"PROFESSIONAL\" | \"ENTERPRISE\";\n\nexport type PartnerTier = \"REGISTERED\" | \"SELECT\" | \"PREMIER\";\n\nexport type InvitationStatus =\n \"PENDING\" | \"ACCEPTED\" | \"REJECTED\" | \"EXPIRED\" | \"CANCELLED\";\n\nexport interface DeveloperOrganization {\n id: string;\n name: string;\n slug: string;\n kind?: \"DIGITAL\" | \"PHYSICAL\";\n description?: string;\n logoUrl?: string;\n bannerUrl?: string;\n websiteUrl?: string;\n contactEmail?: string;\n supportEmail?: string;\n tier: DeveloperOrgTier;\n partnerTier?: PartnerTier;\n partnerSince?: string;\n isVerified: boolean;\n verifiedAt?: string;\n verificationMethod?: string;\n verificationRequestedAt?: string;\n verificationStatus?: \"UNVERIFIED\" | \"PENDING\" | \"VERIFIED\" | \"REJECTED\";\n verificationRequestNotes?: string;\n verificationEvidenceUrl?: string;\n verificationDomain?: string;\n verificationRequestedBy?: string;\n verificationReviewedAt?: string;\n verificationReviewedBy?: string;\n verificationReviewNotes?: string;\n verificationRejectedReason?: string;\n isCertified: boolean;\n certifiedAt?: string;\n certificationExpiry?: string;\n totalApps: number;\n publishedApps: number;\n totalInstalls: number;\n activeInstalls: number;\n totalApiCalls: string;\n averageRating?: number;\n totalRevenue: number;\n totalPlatformFees: number;\n totalPayouts: number;\n pendingBalance: number;\n lifetimeRevenue: number;\n platformFeeRate: number;\n apiCallsLimit: number;\n storageLimit: string;\n apiCallsUsed: string;\n storageUsed: string;\n quotaResetAt?: string;\n taxId?: string;\n taxCountry?: string;\n taxFormType?: string;\n taxFormVerified: boolean;\n legalEntityName?: string;\n legalEntityType?: string;\n legalAddress?: Record<string, unknown>;\n settings?: Record<string, unknown>;\n billingAccountId?: string;\n billingLinkedAt?: string;\n pickupPostalCode?: string;\n createdAt: string;\n updatedAt: string;\n members?: DeveloperOrgMember[];\n invitations?: DeveloperOrgInvitation[];\n applications?: Application[];\n}\n\nexport interface DeveloperOrgMember {\n id: string;\n organizationId: string;\n userId: string;\n email?: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy?: string;\n invitationId?: string;\n isActive: boolean;\n isSuspended: boolean;\n suspendedAt?: string;\n suspendedReason?: string;\n joinedAt: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface DeveloperOrgInvitation {\n id: string;\n organizationId: string;\n invitedUserId?: string;\n invitedEmail: string;\n role: DeveloperOrgRole;\n permissions?: Record<string, unknown>;\n invitedBy: string;\n message?: string;\n token: string;\n status: InvitationStatus;\n expiresAt: string;\n respondedAt?: string;\n memberId?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// ANALYTICS TYPES (MOCKED)\n// =============================================================================\n\nexport interface ApplicationAnalytics {\n id: string;\n applicationId: string;\n date: string;\n period: string;\n activeUsers: number;\n newUsers: number;\n apiCalls: number;\n errors: number;\n avgResponseTime?: number;\n sessions: number;\n avgSessionDuration?: number;\n createdAt: string;\n}\n\nexport interface DeveloperOrgRevenueAnalytics {\n id: string;\n organizationId: string;\n period: string;\n periodStart: string;\n periodEnd: string;\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n salesCount: number;\n refundsCount: number;\n refundsAmount: number;\n newSubscribers: number;\n churnedSubscribers: number;\n activeSubscribers: number;\n avgRevenuePerUser: number;\n topApps?: Array<{ appId: string; revenue: number }>;\n createdAt: string;\n}\n\n// =============================================================================\n// MONETIZATION TYPES (MOCKED)\n// =============================================================================\n\nexport type DevPortalPricingModel =\n | \"FREE\"\n | \"FREEMIUM\"\n | \"SUBSCRIPTION\"\n | \"ONE_TIME\"\n | \"USAGE_BASED\"\n | \"ENTERPRISE\";\n\nexport type SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\nexport type PayoutStatus =\n \"PENDING\" | \"PROCESSING\" | \"COMPLETED\" | \"FAILED\" | \"CANCELLED\";\n\nexport interface DeveloperOrgSale {\n id: string;\n organizationId: string;\n applicationId: string;\n buyerUserId: string;\n buyerTenantId?: string;\n installationScope: string;\n pricingModel: DevPortalPricingModel;\n planName?: string;\n grossAmount: number;\n platformFeeRate: number;\n platformFee: number;\n netAmount: number;\n currency: string;\n taxAmount?: number;\n discountAmount?: number;\n status: SaleStatus;\n createdAt: string;\n}\n\nexport interface DeveloperOrgPayout {\n id: string;\n organizationId: string;\n periodStart: string;\n periodEnd: string;\n grossAmount: number;\n platformFees: number;\n netAmount: number;\n currency: string;\n status: PayoutStatus;\n processedAt?: string;\n failedAt?: string;\n failureReason?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// APP INSTALLATION TYPES\n// =============================================================================\n\nexport type AppInstallationStatus =\n \"ACTIVE\" | \"SUSPENDED\" | \"TRIAL\" | \"EXPIRED\" | \"UNINSTALLED\";\n\nexport interface AppInstallation {\n id: string;\n applicationId: string;\n userId: string;\n tenantId?: string;\n workspaceId?: string;\n organizationId?: string;\n scope: string;\n installedVersion: string;\n status: AppInstallationStatus;\n grantedScopes: string[];\n settings?: Record<string, unknown>;\n installedAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// REVIEW TYPES\n// =============================================================================\n\nexport type ReviewStatus =\n \"PENDING\" | \"PUBLISHED\" | \"HIDDEN\" | \"FLAGGED\" | \"REMOVED\";\n\nexport interface AppReview {\n id: string;\n applicationId: string;\n userId: string;\n rating: number;\n title?: string;\n body?: string;\n status: ReviewStatus;\n isFeatured: boolean;\n helpfulCount: number;\n notHelpfulCount: number;\n developerResponse?: string;\n developerResponseAt?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// =============================================================================\n// DELEGATION TYPES\n// =============================================================================\n\nexport type DelegationStatus = \"ACTIVE\" | \"REVOKED\" | \"EXPIRED\";\n\nexport interface AppUserDelegation {\n id: string;\n appId: string;\n app?: {\n id: string;\n name: string;\n description?: string;\n type: string;\n status: string;\n };\n userId: string;\n scopes: string[];\n workspaceIds: string[];\n status: DelegationStatus;\n grantedAt: string;\n grantedBy: string;\n expiresAt?: string;\n revokedAt?: string;\n revokedBy?: string;\n}\n\nexport interface GrantDelegationInput {\n appId: string;\n userId?: string;\n workspaceIds: string[];\n scopes: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUTH APP TYPES (from auth-svc layer)\n// =============================================================================\n\nexport type AuthAppType =\n | \"ANALYST\"\n | \"API_INTEGRATION\"\n | \"ASSISTANT\"\n | \"AUTOMATION\"\n | \"BOT\"\n | \"CLI_TOOL\"\n | \"DESKTOP_APP\"\n | \"INTEGRATION\"\n | \"MOBILE_APP\"\n | \"SERVICE\"\n | \"SPECIALIST\"\n | \"WEB_APP\"\n | \"WIDGET\"\n | \"WORKFLOW\";\nexport type AuthAppStatus = \"ACTIVE\" | \"INACTIVE\" | \"REVOKED\";\n\nexport interface AuthApp {\n id: string;\n name: string;\n description?: string;\n clientId: string;\n type: AuthAppType;\n status: AuthAppStatus;\n scopes: string[];\n redirectUris: string[];\n ownerUserId: string;\n tenantId: string;\n delegations?: AppUserDelegation[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AuthAppCreateResponse {\n app: AuthApp;\n clientSecret: string;\n}\n\nexport interface CreateAuthAppInput {\n name: string;\n description?: string;\n type: AuthAppType;\n scopes: string[];\n redirectUris?: string[];\n tenantId?: string;\n}\n\nexport interface UpdateAuthAppInput {\n name?: string;\n description?: string;\n scopes?: string[];\n redirectUris?: string[];\n}\n\n// =============================================================================\n// AUTH API KEY TYPES (from auth-svc layer)\n// =============================================================================\n\nexport interface AuthApiKey {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string;\n createdAt: string;\n expiresAt?: string;\n lastUsedAt?: string;\n}\n\nexport interface AuthApiKeyResult {\n apiKey: string;\n apiSecret: string;\n keyId: string;\n}\n\nexport interface AuthApiKeyRotateResult {\n apiSecret: string;\n}\n\nexport interface GenerateAuthApiKeyInput {\n appId: string;\n name: string;\n scope: string[];\n expiresAt?: string;\n}\n\n// =============================================================================\n// AUDIT LOG TYPES\n// =============================================================================\n\nexport interface AuditLog {\n id: string;\n actorId: string;\n actorType: string;\n tenantId: string;\n action: string;\n resource: string;\n resourceId?: string;\n changes?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n createdAt: string;\n}\n\n// =============================================================================\n// VIEW TYPES\n// =============================================================================\n\nexport type DevportalView =\n | \"dashboard\"\n | \"organizations\"\n | \"apps\"\n | \"oauth\"\n | \"api-keys\"\n | \"webhooks\"\n | \"delegations\"\n | \"analytics\"\n | \"revenue\"\n | \"docs\"\n | \"profile\"\n | \"settings\";\n\n// =============================================================================\n// FILTER TYPES\n// =============================================================================\n\nexport interface ApplicationFilter {\n search?: string;\n status?: AppStatus;\n visibility?: AppVisibility;\n appType?: AppType;\n organizationId?: string;\n isFeatured?: boolean;\n}\n\nexport interface WebhookFilter {\n status?: WebhookStatus;\n event?: string;\n}\n\n// =============================================================================\n// WEBHOOK EVENT TYPES\n// =============================================================================\n\nexport const WEBHOOK_EVENTS = [\n // Application events\n \"application.created\",\n \"application.updated\",\n \"application.deleted\",\n \"application.published\",\n \"application.unpublished\",\n \"application.suspended\",\n // Installation events\n \"installation.created\",\n \"installation.updated\",\n \"installation.deleted\",\n // User events\n \"user.created\",\n \"user.updated\",\n \"user.deleted\",\n // Workspace events\n \"workspace.created\",\n \"workspace.updated\",\n \"workspace.deleted\",\n // Project events\n \"project.created\",\n \"project.updated\",\n \"project.deleted\",\n // Task events\n \"task.created\",\n \"task.updated\",\n \"task.deleted\",\n \"task.completed\",\n // Comment events\n \"comment.created\",\n \"comment.updated\",\n \"comment.deleted\",\n // File events\n \"file.uploaded\",\n \"file.deleted\",\n // Member events\n \"member.added\",\n \"member.removed\",\n \"member.role_changed\",\n] as const;\n\nexport type WebhookEvent = (typeof WEBHOOK_EVENTS)[number];\n\n// =============================================================================\n// SCOPE DEFINITIONS\n// =============================================================================\n\nexport const AVAILABLE_SCOPES = [\n // User scopes\n {\n scope: \"user:read\",\n description: \"Read user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:write\",\n description: \"Modify user profile information\",\n category: \"User\",\n },\n {\n scope: \"user:email\",\n description: \"Access user email address\",\n category: \"User\",\n },\n // Workspace scopes\n {\n scope: \"workspace:read\",\n description: \"Read workspace information\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:write\",\n description: \"Create and modify workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:delete\",\n description: \"Delete workspaces\",\n category: \"Workspace\",\n },\n {\n scope: \"workspace:members\",\n description: \"Manage workspace members\",\n category: \"Workspace\",\n },\n // Project scopes\n {\n scope: \"project:read\",\n description: \"Read project information\",\n category: \"Project\",\n },\n {\n scope: \"project:write\",\n description: \"Create and modify projects\",\n category: \"Project\",\n },\n {\n scope: \"project:delete\",\n description: \"Delete projects\",\n category: \"Project\",\n },\n // Task scopes\n { scope: \"task:read\", description: \"Read tasks\", category: \"Task\" },\n {\n scope: \"task:write\",\n description: \"Create and modify tasks\",\n category: \"Task\",\n },\n { scope: \"task:delete\", description: \"Delete tasks\", category: \"Task\" },\n // File scopes\n { scope: \"file:read\", description: \"Read files\", category: \"File\" },\n {\n scope: \"file:write\",\n description: \"Upload and modify files\",\n category: \"File\",\n },\n { scope: \"file:delete\", description: \"Delete files\", category: \"File\" },\n // Comment scopes\n { scope: \"comment:read\", description: \"Read comments\", category: \"Comment\" },\n {\n scope: \"comment:write\",\n description: \"Create and modify comments\",\n category: \"Comment\",\n },\n {\n scope: \"comment:delete\",\n description: \"Delete comments\",\n category: \"Comment\",\n },\n // Offline access\n {\n scope: \"offline_access\",\n description: \"Access data while user is offline\",\n category: \"Access\",\n },\n] as const;\n\nexport type AvailableScope = (typeof AVAILABLE_SCOPES)[number][\"scope\"];\n"],"mappings":";AAu2BA,IAAaA,IAAiB,whBAwC9B,GAQaC,IAAmB;CAE9B;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;CAAO;CAClE;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;CAAO;CAEtE;EAAE,OAAO;EAAa,aAAa;EAAc,UAAU;CAAO;CAClE;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EAAE,OAAO;EAAe,aAAa;EAAgB,UAAU;CAAO;CAEtE;EAAE,OAAO;EAAgB,aAAa;EAAiB,UAAU;CAAU;CAC3E;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CACA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;CAEA;EACE,OAAO;EACP,aAAa;EACb,UAAU;CACZ;AACF"}
|