@burdenoff/microfe-devportal 2026.805.3 → 2026.830.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/components/GrantDelegationModal.d.ts.map +1 -1
- package/dist/devportal/components/GrantDelegationModal.js.map +1 -1
- package/dist/devportal/hooks/useGraphQL.d.ts +0 -12
- package/dist/devportal/hooks/useGraphQL.d.ts.map +1 -1
- package/dist/devportal/hooks/useGraphQL.js.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.d.ts.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.js.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/PublishPage.d.ts.map +1 -1
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/types/index.d.ts.map +1 -1
- package/dist/devportal/types/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PublishPage.js","names":[],"sources":["../../../src/devportal/pages/PublishPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useParams } from \"react-router\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n AlertTriangle,\n Send,\n FileText,\n Shield,\n Image,\n Globe,\n Loader2,\n Eye,\n ArrowRight,\n PartyPopper,\n Link,\n Webhook,\n KeyRound,\n} from \"lucide-react\";\nimport { gql } from \"@apollo/client\";\nimport { useQuery } from \"@apollo/client/react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationQuery,\n useSubmitApplicationMutation,\n useSubmitApplicationForReviewMutation,\n} from \"../../generated/global-operations\";\nimport { useDevportalPermission } from \"../hooks\";\n\nconst MY_PUBLISHER_QUERY = gql`\n query PublishPagePublisher {\n myPublisher {\n id\n }\n }\n`;\nconst PUBLISHER_PRODUCTS_QUERY = gql`\n query PublishPageProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId }) {\n id\n productId\n }\n }\n`;\nconst APP_WEBHOOKS_QUERY = gql`\n query PublishPageWebhooks($applicationId: ID!) {\n webhooks(applicationId: $applicationId) {\n id\n }\n }\n`;\n\ninterface ChecklistItem {\n id: string;\n label: string;\n description: string;\n status: \"passed\" | \"failed\" | \"warning\" | \"pending\";\n icon: React.ReactNode;\n actionLabel?: string;\n actionPath?: string;\n}\n\nconst ALREADY_SUBMITTED_STATUSES = [\n \"SUBMITTED\",\n \"IN_REVIEW\",\n \"APPROVED\",\n \"PUBLISHED\",\n];\n\nexport const PublishPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [submitting, setSubmitting] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const [notes, setNotes] = useState(\"\");\n const submitPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_SUBMIT);\n\n const {\n data,\n loading,\n error: queryError,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n fetchPolicy: \"network-only\",\n });\n\n const [submitApplication] = useSubmitApplicationMutation();\n const [submitApplicationForReview] = useSubmitApplicationForReviewMutation();\n\n // Two-step publisher lookup to find linked StoreProduct\n const { data: publisherData } = useQuery<{\n myPublisher: { id: string } | null;\n }>(MY_PUBLISHER_QUERY, { fetchPolicy: \"cache-first\" });\n const publisherId = publisherData?.myPublisher?.id ?? null;\n const { data: productsData } = useQuery<{\n products: { id: string; productId?: string | null }[];\n }>(PUBLISHER_PRODUCTS_QUERY, {\n variables: { publisherId: publisherId ?? \"\" },\n skip: !publisherId,\n fetchPolicy: \"cache-first\",\n });\n const linkedStoreProductId =\n productsData?.products?.find(\n (p: { id: string; productId?: string | null }) => p.productId === appId,\n )?.id ?? null;\n\n const isApiIntegration = data?.application?.appType === \"API_INTEGRATION\";\n const appManifest = data?.application?.manifest as\n Record<string, unknown> | null | undefined;\n const appOauth = appManifest?.[\"oauth\"] as\n Record<string, unknown> | undefined;\n const hasOAuthUrl = !!(\n appOauth?.[\"authorizationUrl\"] &&\n appOauth?.[\"tokenUrl\"] &&\n appOauth?.[\"clientId\"]\n );\n\n const { data: webhooksData } = useQuery<{ webhooks: { id: string }[] }>(\n APP_WEBHOOKS_QUERY,\n {\n variables: { applicationId: appId! },\n skip: !appId || !isApiIntegration,\n fetchPolicy: \"network-only\",\n },\n );\n const webhookCount = webhooksData?.webhooks?.length ?? 0;\n\n const app = data?.application;\n\n useEffect(() => {\n const name = app?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, app?.name]);\n\n const isAlreadySubmitted = ALREADY_SUBMITTED_STATUSES.includes(\n app?.status ?? \"\",\n );\n\n const isPackageType = app?.appType === \"NODE\" || app?.appType === \"WORKFLOW\";\n const publishedVersionCount = app?.versions?.totalCount ?? 0;\n\n const checklist = useMemo((): ChecklistItem[] => {\n if (!app) return [];\n\n const screenshotCount = app.screenshots?.length ?? 0;\n\n const storeListingPath = `/apps/${appId}?tab=store-listing`;\n const versionsPath = `/apps/${appId}/versions`;\n const editPath = `/apps/${appId}/edit#oauth-config`;\n const webhooksPath = `/apps/${appId}/webhooks`;\n\n const items: ChecklistItem[] = [\n {\n id: \"name\",\n label: tr(\"devportal.checklistNameLabel\", \"Name & Short Description\"),\n description: tr(\n \"devportal.checklistNameDesc\",\n \"Name and short description are required for the store card\",\n ),\n status: app.name && app.shortDescription ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"category\",\n label: tr(\"devportal.checklistCategoryLabel\", \"Category\"),\n description: tr(\n \"devportal.checklistCategoryDesc\",\n \"A category helps users find your item in the marketplace\",\n ),\n status: app.category ? \"passed\" : \"failed\",\n icon: <Globe size={20} />,\n actionLabel: \"Edit app info →\",\n actionPath: `/apps/${appId}/edit`,\n },\n {\n id: \"privacy\",\n label: tr(\"devportal.checklistPrivacyLabel\", \"Privacy Policy\"),\n description: tr(\n \"devportal.checklistPrivacyDesc\",\n \"Privacy policy URL is required for store submission\",\n ),\n status: app.privacyPolicyUrl ? \"passed\" : \"failed\",\n icon: <Shield size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"terms\",\n label: tr(\"devportal.checklistTermsLabel\", \"Terms of Service\"),\n description: tr(\n \"devportal.checklistTermsDesc\",\n \"Terms of service URL is required for store submission\",\n ),\n status: app.termsOfServiceUrl ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"icon\",\n label: tr(\"devportal.checklistIconLabel\", \"App Icon\"),\n description: tr(\n \"devportal.checklistIconDesc\",\n \"An icon makes your listing stand out in the marketplace\",\n ),\n status: app.iconUrl ? \"passed\" : \"warning\",\n icon: <Image size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"screenshots\",\n label: tr(\"devportal.checklistScreenshotsLabel\", \"Screenshots\"),\n description: tr(\n \"devportal.checklistScreenshotsDesc\",\n \"At least 3 screenshots are recommended\",\n ),\n status:\n screenshotCount >= 3\n ? \"passed\"\n : screenshotCount > 0\n ? \"warning\"\n : \"warning\",\n icon: <Image size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n ];\n\n if (isPackageType) {\n items.push({\n id: \"version\",\n label: tr(\"devportal.checklistVersionLabel\", \"Published Version\"),\n description: tr(\n \"devportal.checklistVersionDesc\",\n app?.appType === \"WORKFLOW\"\n ? \"At least one published version (manifest JSON) is required before submitting to the store\"\n : \"At least one published version (ZIP + manifest) is required before submitting to the store\",\n ),\n status: publishedVersionCount > 0 ? \"passed\" : \"failed\",\n icon: <CheckCircle size={20} />,\n actionLabel: \"Create a version →\",\n actionPath: versionsPath,\n });\n }\n\n if (app.appType === \"API_INTEGRATION\") {\n items.push({\n id: \"oauth\",\n label: tr(\"devportal.checklistOAuthLabel\", \"OAuth Configuration\"),\n description: tr(\n \"devportal.checklistOAuthDesc\",\n \"Authorization URL, Token URL and Client ID are required so users can connect their accounts. Without these the Connect button does not work.\",\n ),\n status: hasOAuthUrl ? \"passed\" : \"failed\",\n icon: <KeyRound size={20} />,\n actionLabel: \"Configure in Edit App →\",\n actionPath: editPath,\n });\n\n items.push({\n id: \"webhook\",\n label: tr(\"devportal.checklistWebhookLabel\", \"Webhook Endpoint\"),\n description: tr(\n \"devportal.checklistWebhookDesc\",\n \"At least one webhook endpoint must be configured so your server receives platform events after users connect. Without this the integration does nothing.\",\n ),\n status: webhookCount > 0 ? \"passed\" : \"failed\",\n icon: <Webhook size={20} />,\n actionLabel: \"Add a webhook →\",\n actionPath: webhooksPath,\n });\n\n items.push({\n id: \"documentation\",\n label: tr(\"devportal.checklistDocsLabel\", \"Documentation URL\"),\n description: tr(\n \"devportal.checklistDocsDesc\",\n \"Users need to know how to use and configure this integration. A documentation link is required for API integrations.\",\n ),\n status: app.documentationUrl ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Add documentation URL →\",\n actionPath: editPath,\n });\n\n items.push({\n id: \"website\",\n label: tr(\"devportal.checklistWebsiteLabel\", \"Website URL\"),\n description: tr(\n \"devportal.checklistWebsiteDesc\",\n \"Users need to verify who built this integration. A website URL is required so users know where to get support.\",\n ),\n status: app.websiteUrl ? \"passed\" : \"failed\",\n icon: <Link size={20} />,\n actionLabel: \"Add website URL →\",\n actionPath: editPath,\n });\n }\n\n items.push({\n id: \"status\",\n label: tr(\"devportal.checklistStatusLabel\", \"App Status\"),\n description: tr(\n \"devportal.checklistStatusDesc\",\n \"App must be in DRAFT or REJECTED state to submit\",\n ),\n status: [\"DRAFT\", \"REJECTED\"].includes(app.status ?? \"\")\n ? \"passed\"\n : \"warning\",\n icon: <CheckCircle size={20} />,\n });\n\n return items;\n }, [\n app,\n isPackageType,\n publishedVersionCount,\n webhookCount,\n hasOAuthUrl,\n appId,\n ]);\n\n const passedCount = checklist.filter((i) => i.status === \"passed\").length;\n const failedCount = checklist.filter((i) => i.status === \"failed\").length;\n const warningCount = checklist.filter((i) => i.status === \"warning\").length;\n const isSubmittableStatus = [\"DRAFT\", \"REJECTED\"].includes(app?.status ?? \"\");\n const canSubmit =\n !loading &&\n !queryError &&\n failedCount === 0 &&\n !!app &&\n !isAlreadySubmitted &&\n isSubmittableStatus;\n\n const getStatusIcon = (status: ChecklistItem[\"status\"]) => {\n switch (status) {\n case \"passed\":\n return <CheckCircle size={20} className=\"text-status-success-text\" />;\n case \"failed\":\n return <XCircle size={20} className=\"text-status-error-text\" />;\n case \"warning\":\n return <AlertTriangle size={20} className=\"text-status-warning-text\" />;\n default:\n return (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default\" />\n );\n }\n };\n\n const getStatusBg = (status: ChecklistItem[\"status\"]) => {\n switch (status) {\n case \"passed\":\n return \"bg-status-success-bg-subtle border-border-subtle\";\n case \"failed\":\n return \"bg-status-error-bg-subtle border-border-subtle\";\n case \"warning\":\n return \"bg-status-warning-bg-subtle border-border-subtle\";\n default:\n return \"bg-bg-canvas border-border-subtle\";\n }\n };\n\n const handleSubmit = async () => {\n if (!appId) return;\n setSubmitting(true);\n setSubmitError(null);\n try {\n // Submit Application for devportal review (sets Application.status = SUBMITTED)\n const result = await submitApplication({\n variables: { id: appId, notes: notes.trim() || undefined },\n });\n if (result.error) throw result.error;\n\n // Also submit ApplicationForReview to set StoreProduct.status = PENDING_REVIEW\n // so it appears in the store admin review queue at /store/dashboard/submissions\n await submitApplicationForReview({\n variables: { id: appId, notes: notes.trim() || undefined },\n });\n\n setSubmitted(true);\n } catch (err) {\n setSubmitError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.submitFailedDefault\", \"Failed to submit for review\"),\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n if (loading) {\n return (\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={tr(\n \"devportal.loadingApplicationDetails\",\n \"Loading application details...\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"animate-spin h-8 w-8 text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (queryError || !app) {\n return (\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={tr(\n \"devportal.couldNotLoadApplication\",\n \"Could not load application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <Alert\n variant=\"error\"\n title={tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n >\n {queryError?.message ??\n tr(\"devportal.applicationNotFound\", \"Application not found\")}\n </Alert>\n </PageLayout>\n );\n }\n\n if (submitted) {\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex flex-col items-center justify-center py-16 gap-6 text-center\">\n <div className=\"rounded-full bg-status-success-bg p-4\">\n <PartyPopper size={40} className=\"text-status-success-text\" />\n </div>\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-bold\">\n {tr(\"devportal.submittedSuccessTitle\", \"Submitted for Review!\")}\n </h2>\n <p className=\"text-muted-foreground max-w-md\">\n {tr(\n \"devportal.submittedSuccessMsg\",\n \"Your app has been submitted to the marketplace review queue. You'll receive email notifications as the review progresses — typically 3–5 business days.\",\n )}\n </p>\n </div>\n <div className=\"rounded-lg border bg-muted/40 px-6 py-4 text-sm text-left space-y-2 max-w-md w-full\">\n <p className=\"font-medium\">What happens next:</p>\n <ul className=\"space-y-1 text-muted-foreground list-none\">\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n Our team reviews your submission\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n You'll get an email on approval or if changes are needed\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n Once approved, your app goes live in the marketplace\n </li>\n </ul>\n </div>\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\"\n >\n Back to App\n </button>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={`${tr(\"devportal.submitAppForReview\", \"Submit\")} \"${app.name}\" ${tr(\"devportal.forReviewAndPublication\", \"for review and publication\")}`}\n purpose={tr(\n \"devportal.publishPurpose\",\n \"Submitting sends your app to the review team, who check it against the marketplace guidelines before it goes live to users. Make sure the listing, screenshots, and permissions are final — preview the listing first, since changes after approval need another review.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}/listing`)}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-bg-sunken\"\n >\n <Eye size={16} />\n {tr(\"devportal.previewListing\", \"Preview Listing\")}\n </button>\n }\n >\n {isAlreadySubmitted && (\n <Alert\n variant=\"warning\"\n title={tr(\"devportal.alreadySubmittedTitle\", \"Already Submitted\")}\n >\n {tr(\"devportal.alreadySubmittedMsg1\", \"This app is currently in\")}{\" \"}\n <strong>{app.status}</strong>{\" \"}\n {tr(\n \"devportal.alreadySubmittedMsg2\",\n \"state and cannot be re-submitted until the review process is complete or the app is rejected.\",\n )}\n </Alert>\n )}\n\n {/* Progress Summary */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6\">\n <h2 className=\"text-lg font-semibold\">\n {tr(\"devportal.preSubmissionChecklist\", \"Pre-submission Checklist\")}\n </h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"devportal.completeAllRequiredItems\",\n \"Complete all required items before submitting for review\",\n )}\n </p>\n <div className=\"mt-4 flex gap-6\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle size={20} className=\"text-status-success-text\" />\n <span>\n {passedCount} {tr(\"devportal.passed\", \"Passed\")}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <XCircle size={20} className=\"text-status-error-text\" />\n <span>\n {failedCount} {tr(\"devportal.failed\", \"Failed\")}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <AlertTriangle size={20} className=\"text-status-warning-text\" />\n <span>\n {warningCount} {tr(\"devportal.warnings\", \"Warnings\")}\n </span>\n </div>\n </div>\n </div>\n\n {/* Checklist Items */}\n <PageSection>\n <div className=\"space-y-3\">\n {checklist.map((item) => (\n <div\n key={item.id}\n className={`flex items-center gap-4 rounded-lg border p-4 ${getStatusBg(item.status)}`}\n >\n <div className=\"rounded-lg bg-bg-surface p-2\">{item.icon}</div>\n <div className=\"flex-1\">\n <p className=\"font-medium\">{item.label}</p>\n <p className=\"text-sm text-text-secondary\">\n {item.description}\n </p>\n {item.status !== \"passed\" && item.actionPath && (\n <button\n type=\"button\"\n onClick={() =>\n navigate?.(`${basePath}${item.actionPath}`)\n }\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-primary hover:underline\"\n >\n {item.actionLabel ?? \"Fix this\"} <ArrowRight size={11} />\n </button>\n )}\n </div>\n {getStatusIcon(item.status)}\n </div>\n ))}\n </div>\n </PageSection>\n\n {/* Submission Notes */}\n {!isAlreadySubmitted && (\n <PageSection\n title={tr(\"devportal.notesForReviewerTitle\", \"Notes for Reviewer\")}\n description={tr(\n \"devportal.notesForReviewerDesc\",\n \"Optional message to the review team\",\n )}\n >\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.notesForReviewerPlaceholder\",\n \"Describe what your app does, any special review considerations, or testing instructions...\",\n )}\n rows={4}\n maxLength={1000}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n <p className=\"mt-1 text-xs text-text-secondary text-right\">\n {notes.length}/1000\n </p>\n </PageSection>\n )}\n\n {/* Alerts */}\n {submitError && (\n <Alert\n variant=\"error\"\n title={tr(\"devportal.submissionFailedTitle\", \"Submission Failed\")}\n >\n {submitError}\n </Alert>\n )}\n\n {failedCount > 0 && !isAlreadySubmitted && (\n <Alert\n variant=\"error\"\n title={tr(\"devportal.cannotSubmitTitle\", \"Cannot Submit Yet\")}\n >\n {tr(\"devportal.pleaseFixItems1\", \"Please fix\")} {failedCount}{\" \"}\n {tr(\n \"devportal.pleaseFixItems2\",\n \"required item(s) before submitting for review.\",\n )}\n <div className=\"mt-2 flex flex-wrap gap-3\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"text-sm underline hover:no-underline\"\n >\n {tr(\n \"devportal.goToMarketplaceListing\",\n \"Update marketplace listing →\",\n )}\n </button>\n {isPackageType && publishedVersionCount === 0 && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/versions`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n {tr(\"devportal.goToVersions\", \"Add a published version →\")}\n </button>\n )}\n {isApiIntegration && !hasOAuthUrl && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/edit#oauth-config`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n Configure OAuth endpoints →\n </button>\n )}\n {isApiIntegration && webhookCount === 0 && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/webhooks`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n Add a webhook →\n </button>\n )}\n </div>\n </Alert>\n )}\n\n {warningCount > 0 && failedCount === 0 && !isAlreadySubmitted && (\n <Alert\n variant=\"warning\"\n title={tr(\"devportal.warningsTitle\", \"Warnings\")}\n >\n {tr(\"devportal.warningsMsg1\", \"You have\")} {warningCount}{\" \"}\n {tr(\n \"devportal.warningsMsg2\",\n \"warning(s). While you can submit, addressing these may improve your app's chances of approval.\",\n )}\n </Alert>\n )}\n\n {/* Review Process Info */}\n <PageSection\n title={tr(\"devportal.reviewProcessTitle\", \"Review Process\")}\n >\n <div className=\"rounded-lg border bg-bg-sunken/50 p-4\">\n <ul className=\"space-y-2 text-sm\">\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo1\",\n \"Review typically takes 3-5 business days\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo2\",\n \"You'll receive notifications about review status changes\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo3\",\n \"If changes are requested, you can resubmit without losing your place in the queue\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo4\",\n \"Once approved and published, your app will be visible in the marketplace\",\n )}\n </span>\n </li>\n </ul>\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken\"\n >\n {tr(\"devportal.cancelButton\", \"Cancel\")}\n </button>\n <button\n onClick={handleSubmit}\n disabled={!canSubmit || submitting || !submitPerm.allowed}\n aria-disabled={!canSubmit || submitting || !submitPerm.allowed}\n title={\n !submitPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {submitting ? (\n <>\n <Loader2 size={16} className=\"animate-spin\" />\n {tr(\"devportal.submittingButton\", \"Submitting...\")}\n </>\n ) : (\n <>\n <Send size={16} />\n {tr(\"devportal.submitForReviewButton\", \"Submit for Review\")}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PublishPage.js","names":[],"sources":["../../../src/devportal/pages/PublishPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useParams } from \"react-router\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n AlertTriangle,\n Send,\n FileText,\n Shield,\n Image,\n Globe,\n Loader2,\n Eye,\n ArrowRight,\n PartyPopper,\n Link,\n Webhook,\n KeyRound,\n} from \"lucide-react\";\nimport { gql } from \"@apollo/client\";\nimport { useQuery } from \"@apollo/client/react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { Alert } from \"../components/ConfirmDialog\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationQuery,\n useSubmitApplicationMutation,\n useSubmitApplicationForReviewMutation,\n} from \"../../generated/global-operations\";\nimport { useDevportalPermission } from \"../hooks\";\n\nconst MY_PUBLISHER_QUERY = gql`\n query PublishPagePublisher {\n myPublisher {\n id\n }\n }\n`;\nconst PUBLISHER_PRODUCTS_QUERY = gql`\n query PublishPageProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId }) {\n id\n productId\n }\n }\n`;\nconst APP_WEBHOOKS_QUERY = gql`\n query PublishPageWebhooks($applicationId: ID!) {\n webhooks(applicationId: $applicationId) {\n id\n }\n }\n`;\n\ninterface ChecklistItem {\n id: string;\n label: string;\n description: string;\n status: \"passed\" | \"failed\" | \"warning\" | \"pending\";\n icon: React.ReactNode;\n actionLabel?: string;\n actionPath?: string;\n}\n\nconst ALREADY_SUBMITTED_STATUSES = [\n \"SUBMITTED\",\n \"IN_REVIEW\",\n \"APPROVED\",\n \"PUBLISHED\",\n];\n\nexport const PublishPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [submitting, setSubmitting] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const [notes, setNotes] = useState(\"\");\n const submitPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_SUBMIT);\n\n const {\n data,\n loading,\n error: queryError,\n } = useGetApplicationQuery({\n variables: { id: appId! },\n skip: !appId,\n fetchPolicy: \"network-only\",\n });\n\n const [submitApplication] = useSubmitApplicationMutation();\n const [submitApplicationForReview] = useSubmitApplicationForReviewMutation();\n\n // Two-step publisher lookup to find linked StoreProduct\n const { data: publisherData } = useQuery<{\n myPublisher: { id: string } | null;\n }>(MY_PUBLISHER_QUERY, { fetchPolicy: \"cache-first\" });\n const publisherId = publisherData?.myPublisher?.id ?? null;\n const { data: productsData } = useQuery<{\n products: { id: string; productId?: string | null }[];\n }>(PUBLISHER_PRODUCTS_QUERY, {\n variables: { publisherId: publisherId ?? \"\" },\n skip: !publisherId,\n fetchPolicy: \"cache-first\",\n });\n const linkedStoreProductId =\n productsData?.products?.find(\n (p: { id: string; productId?: string | null }) => p.productId === appId,\n )?.id ?? null;\n\n const isApiIntegration = data?.application?.appType === \"API_INTEGRATION\";\n const appManifest = data?.application?.manifest as\n | Record<string, unknown>\n | null\n | undefined;\n const appOauth = appManifest?.[\"oauth\"] as\n | Record<string, unknown>\n | undefined;\n const hasOAuthUrl = !!(\n appOauth?.[\"authorizationUrl\"] &&\n appOauth?.[\"tokenUrl\"] &&\n appOauth?.[\"clientId\"]\n );\n\n const { data: webhooksData } = useQuery<{ webhooks: { id: string }[] }>(\n APP_WEBHOOKS_QUERY,\n {\n variables: { applicationId: appId! },\n skip: !appId || !isApiIntegration,\n fetchPolicy: \"network-only\",\n },\n );\n const webhookCount = webhooksData?.webhooks?.length ?? 0;\n\n const app = data?.application;\n\n useEffect(() => {\n const name = app?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, app?.name]);\n\n const isAlreadySubmitted = ALREADY_SUBMITTED_STATUSES.includes(\n app?.status ?? \"\",\n );\n\n const isPackageType = app?.appType === \"NODE\" || app?.appType === \"WORKFLOW\";\n const publishedVersionCount = app?.versions?.totalCount ?? 0;\n\n const checklist = useMemo((): ChecklistItem[] => {\n if (!app) return [];\n\n const screenshotCount = app.screenshots?.length ?? 0;\n\n const storeListingPath = `/apps/${appId}?tab=store-listing`;\n const versionsPath = `/apps/${appId}/versions`;\n const editPath = `/apps/${appId}/edit#oauth-config`;\n const webhooksPath = `/apps/${appId}/webhooks`;\n\n const items: ChecklistItem[] = [\n {\n id: \"name\",\n label: tr(\"devportal.checklistNameLabel\", \"Name & Short Description\"),\n description: tr(\n \"devportal.checklistNameDesc\",\n \"Name and short description are required for the store card\",\n ),\n status: app.name && app.shortDescription ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"category\",\n label: tr(\"devportal.checklistCategoryLabel\", \"Category\"),\n description: tr(\n \"devportal.checklistCategoryDesc\",\n \"A category helps users find your item in the marketplace\",\n ),\n status: app.category ? \"passed\" : \"failed\",\n icon: <Globe size={20} />,\n actionLabel: \"Edit app info →\",\n actionPath: `/apps/${appId}/edit`,\n },\n {\n id: \"privacy\",\n label: tr(\"devportal.checklistPrivacyLabel\", \"Privacy Policy\"),\n description: tr(\n \"devportal.checklistPrivacyDesc\",\n \"Privacy policy URL is required for store submission\",\n ),\n status: app.privacyPolicyUrl ? \"passed\" : \"failed\",\n icon: <Shield size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"terms\",\n label: tr(\"devportal.checklistTermsLabel\", \"Terms of Service\"),\n description: tr(\n \"devportal.checklistTermsDesc\",\n \"Terms of service URL is required for store submission\",\n ),\n status: app.termsOfServiceUrl ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"icon\",\n label: tr(\"devportal.checklistIconLabel\", \"App Icon\"),\n description: tr(\n \"devportal.checklistIconDesc\",\n \"An icon makes your listing stand out in the marketplace\",\n ),\n status: app.iconUrl ? \"passed\" : \"warning\",\n icon: <Image size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n {\n id: \"screenshots\",\n label: tr(\"devportal.checklistScreenshotsLabel\", \"Screenshots\"),\n description: tr(\n \"devportal.checklistScreenshotsDesc\",\n \"At least 3 screenshots are recommended\",\n ),\n status:\n screenshotCount >= 3\n ? \"passed\"\n : screenshotCount > 0\n ? \"warning\"\n : \"warning\",\n icon: <Image size={20} />,\n actionLabel: \"Edit store listing →\",\n actionPath: storeListingPath,\n },\n ];\n\n if (isPackageType) {\n items.push({\n id: \"version\",\n label: tr(\"devportal.checklistVersionLabel\", \"Published Version\"),\n description: tr(\n \"devportal.checklistVersionDesc\",\n app?.appType === \"WORKFLOW\"\n ? \"At least one published version (manifest JSON) is required before submitting to the store\"\n : \"At least one published version (ZIP + manifest) is required before submitting to the store\",\n ),\n status: publishedVersionCount > 0 ? \"passed\" : \"failed\",\n icon: <CheckCircle size={20} />,\n actionLabel: \"Create a version →\",\n actionPath: versionsPath,\n });\n }\n\n if (app.appType === \"API_INTEGRATION\") {\n items.push({\n id: \"oauth\",\n label: tr(\"devportal.checklistOAuthLabel\", \"OAuth Configuration\"),\n description: tr(\n \"devportal.checklistOAuthDesc\",\n \"Authorization URL, Token URL and Client ID are required so users can connect their accounts. Without these the Connect button does not work.\",\n ),\n status: hasOAuthUrl ? \"passed\" : \"failed\",\n icon: <KeyRound size={20} />,\n actionLabel: \"Configure in Edit App →\",\n actionPath: editPath,\n });\n\n items.push({\n id: \"webhook\",\n label: tr(\"devportal.checklistWebhookLabel\", \"Webhook Endpoint\"),\n description: tr(\n \"devportal.checklistWebhookDesc\",\n \"At least one webhook endpoint must be configured so your server receives platform events after users connect. Without this the integration does nothing.\",\n ),\n status: webhookCount > 0 ? \"passed\" : \"failed\",\n icon: <Webhook size={20} />,\n actionLabel: \"Add a webhook →\",\n actionPath: webhooksPath,\n });\n\n items.push({\n id: \"documentation\",\n label: tr(\"devportal.checklistDocsLabel\", \"Documentation URL\"),\n description: tr(\n \"devportal.checklistDocsDesc\",\n \"Users need to know how to use and configure this integration. A documentation link is required for API integrations.\",\n ),\n status: app.documentationUrl ? \"passed\" : \"failed\",\n icon: <FileText size={20} />,\n actionLabel: \"Add documentation URL →\",\n actionPath: editPath,\n });\n\n items.push({\n id: \"website\",\n label: tr(\"devportal.checklistWebsiteLabel\", \"Website URL\"),\n description: tr(\n \"devportal.checklistWebsiteDesc\",\n \"Users need to verify who built this integration. A website URL is required so users know where to get support.\",\n ),\n status: app.websiteUrl ? \"passed\" : \"failed\",\n icon: <Link size={20} />,\n actionLabel: \"Add website URL →\",\n actionPath: editPath,\n });\n }\n\n items.push({\n id: \"status\",\n label: tr(\"devportal.checklistStatusLabel\", \"App Status\"),\n description: tr(\n \"devportal.checklistStatusDesc\",\n \"App must be in DRAFT or REJECTED state to submit\",\n ),\n status: [\"DRAFT\", \"REJECTED\"].includes(app.status ?? \"\")\n ? \"passed\"\n : \"warning\",\n icon: <CheckCircle size={20} />,\n });\n\n return items;\n }, [\n app,\n isPackageType,\n publishedVersionCount,\n webhookCount,\n hasOAuthUrl,\n appId,\n ]);\n\n const passedCount = checklist.filter((i) => i.status === \"passed\").length;\n const failedCount = checklist.filter((i) => i.status === \"failed\").length;\n const warningCount = checklist.filter((i) => i.status === \"warning\").length;\n const isSubmittableStatus = [\"DRAFT\", \"REJECTED\"].includes(app?.status ?? \"\");\n const canSubmit =\n !loading &&\n !queryError &&\n failedCount === 0 &&\n !!app &&\n !isAlreadySubmitted &&\n isSubmittableStatus;\n\n const getStatusIcon = (status: ChecklistItem[\"status\"]) => {\n switch (status) {\n case \"passed\":\n return <CheckCircle size={20} className=\"text-status-success-text\" />;\n case \"failed\":\n return <XCircle size={20} className=\"text-status-error-text\" />;\n case \"warning\":\n return <AlertTriangle size={20} className=\"text-status-warning-text\" />;\n default:\n return (\n <div className=\"h-5 w-5 rounded-full border-2 border-border-default\" />\n );\n }\n };\n\n const getStatusBg = (status: ChecklistItem[\"status\"]) => {\n switch (status) {\n case \"passed\":\n return \"bg-status-success-bg-subtle border-border-subtle\";\n case \"failed\":\n return \"bg-status-error-bg-subtle border-border-subtle\";\n case \"warning\":\n return \"bg-status-warning-bg-subtle border-border-subtle\";\n default:\n return \"bg-bg-canvas border-border-subtle\";\n }\n };\n\n const handleSubmit = async () => {\n if (!appId) return;\n setSubmitting(true);\n setSubmitError(null);\n try {\n // Submit Application for devportal review (sets Application.status = SUBMITTED)\n const result = await submitApplication({\n variables: { id: appId, notes: notes.trim() || undefined },\n });\n if (result.error) throw result.error;\n\n // Also submit ApplicationForReview to set StoreProduct.status = PENDING_REVIEW\n // so it appears in the store admin review queue at /store/dashboard/submissions\n await submitApplicationForReview({\n variables: { id: appId, notes: notes.trim() || undefined },\n });\n\n setSubmitted(true);\n } catch (err) {\n setSubmitError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.submitFailedDefault\", \"Failed to submit for review\"),\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n if (loading) {\n return (\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={tr(\n \"devportal.loadingApplicationDetails\",\n \"Loading application details...\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"animate-spin h-8 w-8 text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (queryError || !app) {\n return (\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={tr(\n \"devportal.couldNotLoadApplication\",\n \"Could not load application\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <Alert\n variant=\"error\"\n title={tr(\n \"devportal.failedToLoadApplication\",\n \"Failed to load application\",\n )}\n >\n {queryError?.message ??\n tr(\"devportal.applicationNotFound\", \"Application not found\")}\n </Alert>\n </PageLayout>\n );\n }\n\n if (submitted) {\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n >\n <div className=\"flex flex-col items-center justify-center py-16 gap-6 text-center\">\n <div className=\"rounded-full bg-status-success-bg p-4\">\n <PartyPopper size={40} className=\"text-status-success-text\" />\n </div>\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-bold\">\n {tr(\"devportal.submittedSuccessTitle\", \"Submitted for Review!\")}\n </h2>\n <p className=\"text-muted-foreground max-w-md\">\n {tr(\n \"devportal.submittedSuccessMsg\",\n \"Your app has been submitted to the marketplace review queue. You'll receive email notifications as the review progresses — typically 3–5 business days.\",\n )}\n </p>\n </div>\n <div className=\"rounded-lg border bg-muted/40 px-6 py-4 text-sm text-left space-y-2 max-w-md w-full\">\n <p className=\"font-medium\">What happens next:</p>\n <ul className=\"space-y-1 text-muted-foreground list-none\">\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n Our team reviews your submission\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n You'll get an email on approval or if changes are needed\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle\n size={14}\n className=\"mt-0.5 text-status-success-text shrink-0\"\n />{\" \"}\n Once approved, your app goes live in the marketplace\n </li>\n </ul>\n </div>\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\"\n >\n Back to App\n </button>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.publishToStoreTitle\", \"Publish to Store\")}\n description={`${tr(\"devportal.submitAppForReview\", \"Submit\")} \"${app.name}\" ${tr(\"devportal.forReviewAndPublication\", \"for review and publication\")}`}\n purpose={tr(\n \"devportal.publishPurpose\",\n \"Submitting sends your app to the review team, who check it against the marketplace guidelines before it goes live to users. Make sure the listing, screenshots, and permissions are final — preview the listing first, since changes after approval need another review.\",\n )}\n backLink={`/apps/${appId}`}\n backLabel={tr(\"devportal.backToApplication\", \"Back to Application\")}\n actions={\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}/listing`)}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-bg-sunken\"\n >\n <Eye size={16} />\n {tr(\"devportal.previewListing\", \"Preview Listing\")}\n </button>\n }\n >\n {isAlreadySubmitted && (\n <Alert\n variant=\"warning\"\n title={tr(\"devportal.alreadySubmittedTitle\", \"Already Submitted\")}\n >\n {tr(\"devportal.alreadySubmittedMsg1\", \"This app is currently in\")}{\" \"}\n <strong>{app.status}</strong>{\" \"}\n {tr(\n \"devportal.alreadySubmittedMsg2\",\n \"state and cannot be re-submitted until the review process is complete or the app is rejected.\",\n )}\n </Alert>\n )}\n\n {/* Progress Summary */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6\">\n <h2 className=\"text-lg font-semibold\">\n {tr(\"devportal.preSubmissionChecklist\", \"Pre-submission Checklist\")}\n </h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n \"devportal.completeAllRequiredItems\",\n \"Complete all required items before submitting for review\",\n )}\n </p>\n <div className=\"mt-4 flex gap-6\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle size={20} className=\"text-status-success-text\" />\n <span>\n {passedCount} {tr(\"devportal.passed\", \"Passed\")}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <XCircle size={20} className=\"text-status-error-text\" />\n <span>\n {failedCount} {tr(\"devportal.failed\", \"Failed\")}\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <AlertTriangle size={20} className=\"text-status-warning-text\" />\n <span>\n {warningCount} {tr(\"devportal.warnings\", \"Warnings\")}\n </span>\n </div>\n </div>\n </div>\n\n {/* Checklist Items */}\n <PageSection>\n <div className=\"space-y-3\">\n {checklist.map((item) => (\n <div\n key={item.id}\n className={`flex items-center gap-4 rounded-lg border p-4 ${getStatusBg(item.status)}`}\n >\n <div className=\"rounded-lg bg-bg-surface p-2\">{item.icon}</div>\n <div className=\"flex-1\">\n <p className=\"font-medium\">{item.label}</p>\n <p className=\"text-sm text-text-secondary\">\n {item.description}\n </p>\n {item.status !== \"passed\" && item.actionPath && (\n <button\n type=\"button\"\n onClick={() =>\n navigate?.(`${basePath}${item.actionPath}`)\n }\n className=\"mt-1 inline-flex items-center gap-1 text-xs text-primary hover:underline\"\n >\n {item.actionLabel ?? \"Fix this\"} <ArrowRight size={11} />\n </button>\n )}\n </div>\n {getStatusIcon(item.status)}\n </div>\n ))}\n </div>\n </PageSection>\n\n {/* Submission Notes */}\n {!isAlreadySubmitted && (\n <PageSection\n title={tr(\"devportal.notesForReviewerTitle\", \"Notes for Reviewer\")}\n description={tr(\n \"devportal.notesForReviewerDesc\",\n \"Optional message to the review team\",\n )}\n >\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.notesForReviewerPlaceholder\",\n \"Describe what your app does, any special review considerations, or testing instructions...\",\n )}\n rows={4}\n maxLength={1000}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n <p className=\"mt-1 text-xs text-text-secondary text-right\">\n {notes.length}/1000\n </p>\n </PageSection>\n )}\n\n {/* Alerts */}\n {submitError && (\n <Alert\n variant=\"error\"\n title={tr(\"devportal.submissionFailedTitle\", \"Submission Failed\")}\n >\n {submitError}\n </Alert>\n )}\n\n {failedCount > 0 && !isAlreadySubmitted && (\n <Alert\n variant=\"error\"\n title={tr(\"devportal.cannotSubmitTitle\", \"Cannot Submit Yet\")}\n >\n {tr(\"devportal.pleaseFixItems1\", \"Please fix\")} {failedCount}{\" \"}\n {tr(\n \"devportal.pleaseFixItems2\",\n \"required item(s) before submitting for review.\",\n )}\n <div className=\"mt-2 flex flex-wrap gap-3\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"text-sm underline hover:no-underline\"\n >\n {tr(\n \"devportal.goToMarketplaceListing\",\n \"Update marketplace listing →\",\n )}\n </button>\n {isPackageType && publishedVersionCount === 0 && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/versions`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n {tr(\"devportal.goToVersions\", \"Add a published version →\")}\n </button>\n )}\n {isApiIntegration && !hasOAuthUrl && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/edit#oauth-config`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n Configure OAuth endpoints →\n </button>\n )}\n {isApiIntegration && webhookCount === 0 && (\n <button\n onClick={() =>\n navigate?.(`${basePath}/apps/${appId}/webhooks`)\n }\n className=\"text-sm underline hover:no-underline\"\n >\n Add a webhook →\n </button>\n )}\n </div>\n </Alert>\n )}\n\n {warningCount > 0 && failedCount === 0 && !isAlreadySubmitted && (\n <Alert\n variant=\"warning\"\n title={tr(\"devportal.warningsTitle\", \"Warnings\")}\n >\n {tr(\"devportal.warningsMsg1\", \"You have\")} {warningCount}{\" \"}\n {tr(\n \"devportal.warningsMsg2\",\n \"warning(s). While you can submit, addressing these may improve your app's chances of approval.\",\n )}\n </Alert>\n )}\n\n {/* Review Process Info */}\n <PageSection\n title={tr(\"devportal.reviewProcessTitle\", \"Review Process\")}\n >\n <div className=\"rounded-lg border bg-bg-sunken/50 p-4\">\n <ul className=\"space-y-2 text-sm\">\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo1\",\n \"Review typically takes 3-5 business days\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo2\",\n \"You'll receive notifications about review status changes\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo3\",\n \"If changes are requested, you can resubmit without losing your place in the queue\",\n )}\n </span>\n </li>\n <li className=\"flex items-start gap-2\">\n <CheckCircle size={16} className=\"mt-0.5 text-primary\" />\n <span>\n {tr(\n \"devportal.reviewProcessInfo4\",\n \"Once approved and published, your app will be visible in the marketplace\",\n )}\n </span>\n </li>\n </ul>\n </div>\n </PageSection>\n\n {/* Actions */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${appId}`)}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken\"\n >\n {tr(\"devportal.cancelButton\", \"Cancel\")}\n </button>\n <button\n onClick={handleSubmit}\n disabled={!canSubmit || submitting || !submitPerm.allowed}\n aria-disabled={!canSubmit || submitting || !submitPerm.allowed}\n title={\n !submitPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {submitting ? (\n <>\n <Loader2 size={16} className=\"animate-spin\" />\n {tr(\"devportal.submittingButton\", \"Submitting...\")}\n </>\n ) : (\n <>\n <Send size={16} />\n {tr(\"devportal.submitForReviewButton\", \"Submit for Review\")}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAmCA,IAAM,KAAqB,CAAG;;;;;;GAOxB,KAA2B,CAAG;;;;;;;GAQ9B,KAAqB,CAAG;;;;;;GAkBxB,KAA6B;CACjC;CACA;CACA;CACA;CACD,EAEY,UAAwB;CACnC,IAAM,EAAE,aAAU,IAA8B,EAC1C,EAAE,aAAU,gBAAa,GAAc,EACvC,EAAE,UAAM,IAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,GAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAE9B,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,IAAa,EAAuB,GAAsB,WAAW,EAErE,EACJ,SACA,YACA,OAAO,MACL,EAAuB;EACzB,WAAW,EAAE,IAAI,GAAQ;EACzB,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,CAAC,MAAqB,GAA8B,EACpD,CAAC,MAA8B,GAAuC,EAGtE,EAAE,MAAM,OAAkB,EAE7B,IAAoB,EAAE,aAAa,eAAe,CAAC,EAChD,IAAc,IAAe,aAAa,MAAM,MAChD,EAAE,MAAM,OAAiB,EAE5B,IAA0B;EAC3B,WAAW,EAAE,aAAa,KAAe,IAAI;EAC7C,MAAM,CAAC;EACP,aAAa;EACd,CAAC;AAEA,KAAc,UAAU,MACrB,MAAiD,EAAE,cAAc,EACnE,EAAE;CAEL,IAAM,IAAmB,GAAM,aAAa,YAAY,mBAKlD,IAJc,GAAM,aAAa,UAIR,OAGzB,IAAc,CAAC,EACnB,GAAW,oBACX,GAAW,YACX,GAAW,WAGP,EAAE,MAAM,OAAiB,EAC7B,IACA;EACE,WAAW,EAAE,eAAe,GAAQ;EACpC,MAAM,CAAC,KAAS,CAAC;EACjB,aAAa;EACd,CACF,EACK,IAAe,IAAc,UAAU,UAAU,GAEjD,IAAM,GAAM;AAElB,SAAgB;EACd,IAAM,IAAO,GAAK;AAClB,MAAI,CAAC,KAAS,CAAC,EAAM;EACrB,IAAM,IACJ,OAMA;AAEF,SADA,GAAK,SAAS,GAAO,EAAK,QACb,GAAK,WAAW,EAAM;IAClC,CAAC,GAAO,GAAK,KAAK,CAAC;CAEtB,IAAM,IAAqB,GAA2B,SACpD,GAAK,UAAU,GAChB,EAEK,IAAgB,GAAK,YAAY,UAAU,GAAK,YAAY,YAC5D,IAAwB,GAAK,UAAU,cAAc,GAErD,IAAY,QAA+B;AAC/C,MAAI,CAAC,EAAK,QAAO,EAAE;EAEnB,IAAM,IAAkB,EAAI,aAAa,UAAU,GAE7C,IAAmB,SAAS,EAAM,qBAClC,IAAe,SAAS,EAAM,YAC9B,IAAW,SAAS,EAAM,qBAC1B,IAAe,SAAS,EAAM,YAE9B,IAAyB;GAC7B;IACE,IAAI;IACJ,OAAO,EAAG,gCAAgC,2BAA2B;IACrE,aAAa,EACX,+BACA,6DACD;IACD,QAAQ,EAAI,QAAQ,EAAI,mBAAmB,WAAW;IACtD,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,OAAO,EAAG,oCAAoC,WAAW;IACzD,aAAa,EACX,mCACA,2DACD;IACD,QAAQ,EAAI,WAAW,WAAW;IAClC,MAAM,kBAAC,IAAD,EAAO,MAAM,IAAM,CAAA;IACzB,aAAa;IACb,YAAY,SAAS,EAAM;IAC5B;GACD;IACE,IAAI;IACJ,OAAO,EAAG,mCAAmC,iBAAiB;IAC9D,aAAa,EACX,kCACA,sDACD;IACD,QAAQ,EAAI,mBAAmB,WAAW;IAC1C,MAAM,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;IAC1B,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,OAAO,EAAG,iCAAiC,mBAAmB;IAC9D,aAAa,EACX,gCACA,wDACD;IACD,QAAQ,EAAI,oBAAoB,WAAW;IAC3C,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,OAAO,EAAG,gCAAgC,WAAW;IACrD,aAAa,EACX,+BACA,0DACD;IACD,QAAQ,EAAI,UAAU,WAAW;IACjC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,OAAO,EAAG,uCAAuC,cAAc;IAC/D,aAAa,EACX,sCACA,yCACD;IACD,QACE,KAAmB,IACf,WAEE;IAER,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,aAAa;IACb,YAAY;IACb;GACF;AAsFD,SApFI,KACF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,mCAAmC,oBAAoB;GACjE,aAAa,EACX,kCACA,GAAK,YAAY,aACb,8FACA,6FACL;GACD,QAAQ,IAAwB,IAAI,WAAW;GAC/C,MAAM,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA;GAC/B,aAAa;GACb,YAAY;GACb,CAAC,EAGA,EAAI,YAAY,sBAClB,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,iCAAiC,sBAAsB;GACjE,aAAa,EACX,gCACA,+IACD;GACD,QAAQ,IAAc,WAAW;GACjC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;GAC5B,aAAa;GACb,YAAY;GACb,CAAC,EAEF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,mCAAmC,mBAAmB;GAChE,aAAa,EACX,kCACA,2JACD;GACD,QAAQ,IAAe,IAAI,WAAW;GACtC,MAAM,kBAAC,IAAD,EAAS,MAAM,IAAM,CAAA;GAC3B,aAAa;GACb,YAAY;GACb,CAAC,EAEF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,gCAAgC,oBAAoB;GAC9D,aAAa,EACX,+BACA,uHACD;GACD,QAAQ,EAAI,mBAAmB,WAAW;GAC1C,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;GAC5B,aAAa;GACb,YAAY;GACb,CAAC,EAEF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,mCAAmC,cAAc;GAC3D,aAAa,EACX,kCACA,iHACD;GACD,QAAQ,EAAI,aAAa,WAAW;GACpC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;GACxB,aAAa;GACb,YAAY;GACb,CAAC,GAGJ,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,kCAAkC,aAAa;GACzD,aAAa,EACX,iCACA,mDACD;GACD,QAAQ,CAAC,SAAS,WAAW,CAAC,SAAS,EAAI,UAAU,GAAG,GACpD,WACA;GACJ,MAAM,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA;GAChC,CAAC,EAEK;IACN;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAEI,KAAc,EAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,QAC7D,IAAc,EAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,QAC7D,IAAe,EAAU,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC,QAC/D,KAAsB,CAAC,SAAS,WAAW,CAAC,SAAS,GAAK,UAAU,GAAG,EACvE,IACJ,CAAC,KACD,CAAC,KACD,MAAgB,KAChB,CAAC,CAAC,KACF,CAAC,KACD,IAEI,MAAiB,MAAoC;AACzD,UAAQ,GAAR;GACE,KAAK,SACH,QAAO,kBAAC,GAAD;IAAa,MAAM;IAAI,WAAU;IAA6B,CAAA;GACvE,KAAK,SACH,QAAO,kBAAC,GAAD;IAAS,MAAM;IAAI,WAAU;IAA2B,CAAA;GACjE,KAAK,UACH,QAAO,kBAAC,GAAD;IAAe,MAAM;IAAI,WAAU;IAA6B,CAAA;GACzE,QACE,QACE,kBAAC,OAAD,EAAK,WAAU,uDAAwD,CAAA;;IAKzE,MAAe,MAAoC;AACvD,UAAQ,GAAR;GACE,KAAK,SACH,QAAO;GACT,KAAK,SACH,QAAO;GACT,KAAK,UACH,QAAO;GACT,QACE,QAAO;;;AAyIb,QAxGI,IAEA,kBAAC,GAAD;EACE,OAAO,EAAG,iCAAiC,mBAAmB;EAC9D,aAAa,EACX,uCACA,iCACD;EACD,UAAU,SAAS;EACnB,WAAW,EAAG,+BAA+B,sBAAsB;YAEnE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;GAC5D,CAAA;EACK,CAAA,GAIb,KAAc,CAAC,IAEf,kBAAC,GAAD;EACE,OAAO,EAAG,iCAAiC,mBAAmB;EAC9D,aAAa,EACX,qCACA,6BACD;EACD,UAAU,SAAS;EACnB,WAAW,EAAG,+BAA+B,sBAAsB;YAEnE,kBAAC,GAAD;GACE,SAAQ;GACR,OAAO,EACL,qCACA,6BACD;aAEA,GAAY,WACX,EAAG,iCAAiC,wBAAwB;GACxD,CAAA;EACG,CAAA,GAIb,IAEA,kBAAC,GAAD;EAAiB,UAAA;YACf,kBAAC,GAAD;GACE,OAAO,EAAG,iCAAiC,mBAAmB;GAC9D,UAAU,SAAS;GACnB,WAAW,EAAG,+BAA+B,sBAAsB;aAEnE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,IAAD;OAAa,MAAM;OAAI,WAAU;OAA6B,CAAA;MAC1D,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,mCAAmC,wBAAwB;OAC5D,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,iCACA,0JACD;OACC,CAAA,CACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAc;OAAsB,CAAA,EACjD,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,MAAD;SAAI,WAAU;mBAAd;UACE,kBAAC,GAAD;WACE,MAAM;WACN,WAAU;WACV,CAAA;UAAC;UAAI;UAEJ;;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAd;UACE,kBAAC,GAAD;WACE,MAAM;WACN,WAAU;WACV,CAAA;UAAC;UAAI;UAEJ;;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAd;UACE,kBAAC,GAAD;WACE,MAAM;WACN,WAAU;WACV,CAAA;UAAC;UAAI;UAEJ;;QACF;SACD;;KACN,kBAAC,UAAD;MACE,eAAe,IAAW,GAAG,EAAS,QAAQ,IAAQ;MACtD,WAAU;gBACX;MAEQ,CAAA;KACL;;GACK,CAAA;EACG,CAAA,GAKpB,kBAAC,GAAD;EAAiB,UAAA;YACf,kBAAC,GAAD;GACE,OAAO,EAAG,iCAAiC,mBAAmB;GAC9D,aAAa,GAAG,EAAG,gCAAgC,SAAS,CAAC,IAAI,EAAI,KAAK,IAAI,EAAG,qCAAqC,6BAA6B;GACnJ,SAAS,EACP,4BACA,2QACD;GACD,UAAU,SAAS;GACnB,WAAW,EAAG,+BAA+B,sBAAsB;GACnE,SACE,kBAAC,UAAD;IACE,eAAe,IAAW,GAAG,EAAS,QAAQ,EAAM,UAAU;IAC9D,WAAU;cAFZ,CAIE,kBAAC,IAAD,EAAK,MAAM,IAAM,CAAA,EAChB,EAAG,4BAA4B,kBAAkB,CAC3C;;aAhBb;IAmBG,KACC,kBAAC,GAAD;KACE,SAAQ;KACR,OAAO,EAAG,mCAAmC,oBAAoB;eAFnE;MAIG,EAAG,kCAAkC,2BAA2B;MAAE;MACnE,kBAAC,UAAD,EAAA,UAAS,EAAI,QAAgB,CAAA;MAAC;MAC7B,EACC,kCACA,gGACD;MACK;;IAIV,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,oCAAoC,2BAA2B;OAChE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,sCACA,2DACD;OACC,CAAA;MACJ,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAa,MAAM;UAAI,WAAU;UAA6B,CAAA,EAC9D,kBAAC,QAAD,EAAA,UAAA;UACG;UAAY;UAAE,EAAG,oBAAoB,SAAS;UAC1C,EAAA,CAAA,CACH;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAS,MAAM;UAAI,WAAU;UAA2B,CAAA,EACxD,kBAAC,QAAD,EAAA,UAAA;UACG;UAAY;UAAE,EAAG,oBAAoB,SAAS;UAC1C,EAAA,CAAA,CACH;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAe,MAAM;UAAI,WAAU;UAA6B,CAAA,EAChE,kBAAC,QAAD,EAAA,UAAA;UACG;UAAa;UAAE,EAAG,sBAAsB,WAAW;UAC/C,EAAA,CAAA,CACH;;QACF;;MACF;;IAGN,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MACd,kBAAC,OAAD;MAEE,WAAW,iDAAiD,GAAY,EAAK,OAAO;gBAFtF;OAIE,kBAAC,OAAD;QAAK,WAAU;kBAAgC,EAAK;QAAW,CAAA;OAC/D,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,KAAD;UAAG,WAAU;oBAAe,EAAK;UAAU,CAAA;SAC3C,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAK;UACJ,CAAA;SACH,EAAK,WAAW,YAAY,EAAK,cAChC,kBAAC,UAAD;UACE,MAAK;UACL,eACE,IAAW,GAAG,IAAW,EAAK,aAAa;UAE7C,WAAU;oBALZ;WAOG,EAAK,eAAe;WAAW;WAAC,kBAAC,IAAD,EAAY,MAAM,IAAM,CAAA;WAClD;;SAEP;;OACL,GAAc,EAAK,OAAO;OACvB;QAtBC,EAAK,GAsBN,CACN;KACE,CAAA,EACM,CAAA;IAGb,CAAC,KACA,kBAAC,GAAD;KACE,OAAO,EAAG,mCAAmC,qBAAqB;KAClE,aAAa,EACX,kCACA,sCACD;eALH,CAOE,kBAAC,YAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;MACzC,aAAa,EACX,yCACA,6FACD;MACD,MAAM;MACN,WAAW;MACX,WAAU;MACV,CAAA,EACF,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACG,EAAM,QAAO,QACZ;QACQ;;IAIf,KACC,kBAAC,GAAD;KACE,SAAQ;KACR,OAAO,EAAG,mCAAmC,oBAAoB;eAEhE;KACK,CAAA;IAGT,IAAc,KAAK,CAAC,KACnB,kBAAC,GAAD;KACE,SAAQ;KACR,OAAO,EAAG,+BAA+B,oBAAoB;eAF/D;MAIG,EAAG,6BAA6B,aAAa;MAAC;MAAE;MAAa;MAC7D,EACC,6BACA,iDACD;MACD,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,eAAe,IAAW,GAAG,EAAS,QAAQ,IAAQ;SACtD,WAAU;mBAET,EACC,oCACA,+BACD;SACM,CAAA;QACR,KAAiB,MAA0B,KAC1C,kBAAC,UAAD;SACE,eACE,IAAW,GAAG,EAAS,QAAQ,EAAM,WAAW;SAElD,WAAU;mBAET,EAAG,0BAA0B,4BAA4B;SACnD,CAAA;QAEV,KAAoB,CAAC,KACpB,kBAAC,UAAD;SACE,eACE,IAAW,GAAG,EAAS,QAAQ,EAAM,oBAAoB;SAE3D,WAAU;mBACX;SAEQ,CAAA;QAEV,KAAoB,MAAiB,KACpC,kBAAC,UAAD;SACE,eACE,IAAW,GAAG,EAAS,QAAQ,EAAM,WAAW;SAElD,WAAU;mBACX;SAEQ,CAAA;QAEP;;MACA;;IAGT,IAAe,KAAK,MAAgB,KAAK,CAAC,KACzC,kBAAC,GAAD;KACE,SAAQ;KACR,OAAO,EAAG,2BAA2B,WAAW;eAFlD;MAIG,EAAG,0BAA0B,WAAW;MAAC;MAAE;MAAc;MACzD,EACC,0BACA,iGACD;MACK;;IAIV,kBAAC,GAAD;KACE,OAAO,EAAG,gCAAgC,iBAAiB;eAE3D,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,GAAD;UAAa,MAAM;UAAI,WAAU;UAAwB,CAAA,EACzD,kBAAC,QAAD,EAAA,UACG,EACC,gCACA,2CACD,EACI,CAAA,CACJ;;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,GAAD;UAAa,MAAM;UAAI,WAAU;UAAwB,CAAA,EACzD,kBAAC,QAAD,EAAA,UACG,EACC,gCACA,2DACD,EACI,CAAA,CACJ;;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,GAAD;UAAa,MAAM;UAAI,WAAU;UAAwB,CAAA,EACzD,kBAAC,QAAD,EAAA,UACG,EACC,gCACA,oFACD,EACI,CAAA,CACJ;;QACL,kBAAC,MAAD;SAAI,WAAU;mBAAd,CACE,kBAAC,GAAD;UAAa,MAAM;UAAI,WAAU;UAAwB,CAAA,EACzD,kBAAC,QAAD,EAAA,UACG,EACC,gCACA,2EACD,EACI,CAAA,CACJ;;QACF;;MACD,CAAA;KACM,CAAA;IAGd,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,eAAe,IAAW,GAAG,EAAS,QAAQ,IAAQ;MACtD,WAAU;gBAET,EAAG,0BAA0B,SAAS;MAChC,CAAA,EACT,kBAAC,UAAD;MACE,SAvYW,YAAY;AAC1B,cAEL;QADA,EAAc,GAAK,EACnB,EAAe,KAAK;AACpB,YAAI;SAEF,IAAM,IAAS,MAAM,GAAkB,EACrC,WAAW;UAAE,IAAI;UAAO,OAAO,EAAM,MAAM,IAAI,KAAA;UAAW,EAC3D,CAAC;AACF,aAAI,EAAO,MAAO,OAAM,EAAO;AAQ/B,SAJA,MAAM,GAA2B,EAC/B,WAAW;UAAE,IAAI;UAAO,OAAO,EAAM,MAAM,IAAI,KAAA;UAAW,EAC3D,CAAC,EAEF,EAAa,GAAK;iBACX,GAAK;AACZ,WACE,aAAe,QACX,EAAI,UACJ,EAAG,iCAAiC,8BAA8B,CACvE;kBACO;AACR,WAAc,GAAM;;;;MA+Wd,UAAU,CAAC,KAAa,KAAc,CAAC,EAAW;MAClD,iBAAe,CAAC,KAAa,KAAc,CAAC,EAAW;MACvD,OACG,EAAW,UAKR,KAAA,IAJA,EACE,iCACA,oDACD;MAGP,WAAU;gBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAAiB,CAAA,EAC7C,EAAG,8BAA8B,gBAAgB,CACjD,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,IAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,mCAAmC,oBAAoB,CAC1D,EAAA,CAAA;MAEE,CAAA,CACL;;IACK;;EACG,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerDashboardPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SellerDashboardPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AA6I3C,eAAO,MAAM,mBAAmB,EAAE,EAwSjC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Package,\n Building2,\n Plus,\n ArrowRight,\n Loader2,\n Download,\n DollarSign,\n Clock,\n CheckCircle2,\n} from \"lucide-react\";\nimport { GlassCard, GradientText } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n \"DRAFT\" | \"PENDING_REVIEW\" | \"PUBLISHED\" | \"REJECTED\" | \"INACTIVE\";\n\ninterface PhysicalProduct {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n icon?: string | null;\n}\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n logoUrl?: string | null;\n isVerified: boolean;\n publishedApps?: number | null;\n totalInstalls?: number | null;\n totalRevenue?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst BOOTSTRAP_QUERY = `\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n }\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_PRODUCTS_QUERY = `\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerDashboardPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [bootstrapLoading, setBootstrapLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProduct[]>([]);\n\n // Fetch orgs + publisher in one round-trip\n useEffect(() => {\n let cancelled = false;\n setBootstrapLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: BOOTSTRAP_QUERY }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n myDeveloperOrganizations?: OrgSummary[];\n myPublisher?: { id: string } | null;\n };\n };\n if (cancelled) return;\n setOrgs(json.data?.myDeveloperOrganizations ?? []);\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } finally {\n if (!cancelled) setBootstrapLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken]);\n\n // Fetch recent physical products once publisher is known\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_PRODUCTS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProduct, \"status\"> & { status: string }\n >;\n };\n };\n if (cancelled) return;\n const DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n setProducts(\n (json.data?.products ?? [])\n .filter((p) => !DIGITAL_TYPES.has(p.type))\n .map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken]);\n\n const isLoading = bootstrapLoading;\n const statsLoading = bootstrapLoading || productsLoading;\n const hasOrgs = !isLoading && orgs.length > 0;\n const hasPublisher = !isLoading && publisherId !== null;\n\n const totalItems = products.length;\n const pendingCount = products.filter(\n (p) => p.status === \"PENDING_REVIEW\",\n ).length;\n const publishedCount = products.filter(\n (p) => p.status === \"PUBLISHED\",\n ).length;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={\n <>\n {tr(\"devportal.seller\", \"Seller\")}{\" \"}\n <GradientText>\n {tr(\"devportal.dashboard\", \"Dashboard\")}\n </GradientText>\n </>\n }\n actions={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n ) : !isLoading ? (\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n <div className=\"flex flex-col gap-8\">\n {/* Compact inline stats */}\n <div className=\"flex items-center gap-6 border-b border-border-seam pb-6\">\n {statsLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n <>\n <StatPill\n icon={<Package size={14} />}\n value={totalItems}\n label={tr(\"devportal.items\", \"Items\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Clock size={14} />}\n value={pendingCount}\n label={tr(\"devportal.pendingReview\", \"Pending Review\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<CheckCircle2 size={14} />}\n value={publishedCount}\n label={tr(\"devportal.published\", \"Published\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Building2 size={14} />}\n value={orgs.length}\n label={tr(\"devportal.organizations\", \"Organizations\")}\n />\n </>\n )}\n </div>\n\n {/* Recent Listings */}\n <DashSection\n title={tr(\"devportal.recentListings\", \"Recent Listings\")}\n action={\n products.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/seller/items\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading || productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : products.length > 0 ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {products.slice(0, 3).map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n onClick={() => handleNavigate(\"/seller/items\")}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={\n !hasPublisher\n ? tr(\n \"devportal.registerAsPublisherFirst\",\n \"Register as publisher first\",\n )\n : tr(\"devportal.listYourFirstItem\", \"List your first item\")\n }\n description={\n !hasPublisher\n ? tr(\n \"devportal.createOrgToStartSelling\",\n \"Create an organization to start selling physical products\",\n )\n : tr(\n \"devportal.addPhysicalProductsToYourStore\",\n \"Add physical products to your store and they will appear here\",\n )\n }\n action={\n !hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n )\n }\n />\n )}\n </DashSection>\n\n {/* Organizations */}\n {(isLoading || orgs.length > 0) && (\n <DashSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n action={\n orgs.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {orgs.slice(0, 3).map((org) => (\n <OrgCard\n key={org.id}\n org={org}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n )}\n </DashSection>\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst StatPill: FC<{ icon: ReactNode; value: number; label: string }> = ({\n icon,\n value,\n label,\n}) => (\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-text-muted\">{icon}</span>\n <span className=\"text-sm font-semibold tabular-nums\">{value}</span>\n <span className=\"text-sm text-text-secondary\">{label}</span>\n </div>\n);\n\nconst DashSection: FC<{\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}> = ({ title, action, children }) => (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold uppercase tracking-wider text-text-secondary\">\n {title}\n </h2>\n {action}\n </div>\n {children}\n </section>\n);\n\nconst ProductCard: FC<{\n product: PhysicalProduct;\n onClick: () => void;\n}> = ({ product, onClick }) => {\n const badge =\n STATUS_BADGE_STYLES[product.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {product.icon ? (\n <img\n src={product.icon}\n alt={product.name}\n className=\"h-10 w-10 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary shrink-0\">\n <Package size={20} />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">\n {product.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {formatPrice(product.price, product.currency)}\n </p>\n </div>\n <span\n className={`shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <Package size={12} className=\"text-text-muted shrink-0\" />\n <span\n className={\n product.stockQuantity === 0\n ? \"text-status-error-text\"\n : \"text-text-secondary\"\n }\n >\n {product.stockQuantity === 0\n ? \"Out of stock\"\n : `${product.stockQuantity} in stock`}\n </span>\n </div>\n </GlassCard>\n );\n};\n\nconst OrgCard: FC<{ org: OrgSummary; onClick: () => void }> = ({\n org,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const initials = org.name\n .split(\" \")\n .map((w: string) => w[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary text-sm font-bold\">\n {initials}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">{org.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {org.publishedApps ?? 0}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n <ArrowRight size={15} className=\"text-text-muted shrink-0\" />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <Download size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.installs\", \"Installs\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n {(org.totalInstalls ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <DollarSign size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.revenue\", \"Revenue\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n ${(org.totalRevenue ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Package,\n Building2,\n Plus,\n ArrowRight,\n Loader2,\n Download,\n DollarSign,\n Clock,\n CheckCircle2,\n} from \"lucide-react\";\nimport { GlassCard, GradientText } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n | \"DRAFT\"\n | \"PENDING_REVIEW\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"INACTIVE\";\n\ninterface PhysicalProduct {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n icon?: string | null;\n}\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n logoUrl?: string | null;\n isVerified: boolean;\n publishedApps?: number | null;\n totalInstalls?: number | null;\n totalRevenue?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst BOOTSTRAP_QUERY = `\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n }\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_PRODUCTS_QUERY = `\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerDashboardPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [bootstrapLoading, setBootstrapLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProduct[]>([]);\n\n // Fetch orgs + publisher in one round-trip\n useEffect(() => {\n let cancelled = false;\n setBootstrapLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: BOOTSTRAP_QUERY }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n myDeveloperOrganizations?: OrgSummary[];\n myPublisher?: { id: string } | null;\n };\n };\n if (cancelled) return;\n setOrgs(json.data?.myDeveloperOrganizations ?? []);\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } finally {\n if (!cancelled) setBootstrapLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken]);\n\n // Fetch recent physical products once publisher is known\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_PRODUCTS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProduct, \"status\"> & { status: string }\n >;\n };\n };\n if (cancelled) return;\n const DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n setProducts(\n (json.data?.products ?? [])\n .filter((p) => !DIGITAL_TYPES.has(p.type))\n .map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken]);\n\n const isLoading = bootstrapLoading;\n const statsLoading = bootstrapLoading || productsLoading;\n const hasOrgs = !isLoading && orgs.length > 0;\n const hasPublisher = !isLoading && publisherId !== null;\n\n const totalItems = products.length;\n const pendingCount = products.filter(\n (p) => p.status === \"PENDING_REVIEW\",\n ).length;\n const publishedCount = products.filter(\n (p) => p.status === \"PUBLISHED\",\n ).length;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={\n <>\n {tr(\"devportal.seller\", \"Seller\")}{\" \"}\n <GradientText>\n {tr(\"devportal.dashboard\", \"Dashboard\")}\n </GradientText>\n </>\n }\n actions={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n ) : !isLoading ? (\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n <div className=\"flex flex-col gap-8\">\n {/* Compact inline stats */}\n <div className=\"flex items-center gap-6 border-b border-border-seam pb-6\">\n {statsLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n <>\n <StatPill\n icon={<Package size={14} />}\n value={totalItems}\n label={tr(\"devportal.items\", \"Items\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Clock size={14} />}\n value={pendingCount}\n label={tr(\"devportal.pendingReview\", \"Pending Review\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<CheckCircle2 size={14} />}\n value={publishedCount}\n label={tr(\"devportal.published\", \"Published\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Building2 size={14} />}\n value={orgs.length}\n label={tr(\"devportal.organizations\", \"Organizations\")}\n />\n </>\n )}\n </div>\n\n {/* Recent Listings */}\n <DashSection\n title={tr(\"devportal.recentListings\", \"Recent Listings\")}\n action={\n products.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/seller/items\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading || productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : products.length > 0 ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {products.slice(0, 3).map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n onClick={() => handleNavigate(\"/seller/items\")}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={\n !hasPublisher\n ? tr(\n \"devportal.registerAsPublisherFirst\",\n \"Register as publisher first\",\n )\n : tr(\"devportal.listYourFirstItem\", \"List your first item\")\n }\n description={\n !hasPublisher\n ? tr(\n \"devportal.createOrgToStartSelling\",\n \"Create an organization to start selling physical products\",\n )\n : tr(\n \"devportal.addPhysicalProductsToYourStore\",\n \"Add physical products to your store and they will appear here\",\n )\n }\n action={\n !hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n )\n }\n />\n )}\n </DashSection>\n\n {/* Organizations */}\n {(isLoading || orgs.length > 0) && (\n <DashSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n action={\n orgs.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {orgs.slice(0, 3).map((org) => (\n <OrgCard\n key={org.id}\n org={org}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n )}\n </DashSection>\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst StatPill: FC<{ icon: ReactNode; value: number; label: string }> = ({\n icon,\n value,\n label,\n}) => (\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-text-muted\">{icon}</span>\n <span className=\"text-sm font-semibold tabular-nums\">{value}</span>\n <span className=\"text-sm text-text-secondary\">{label}</span>\n </div>\n);\n\nconst DashSection: FC<{\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}> = ({ title, action, children }) => (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold uppercase tracking-wider text-text-secondary\">\n {title}\n </h2>\n {action}\n </div>\n {children}\n </section>\n);\n\nconst ProductCard: FC<{\n product: PhysicalProduct;\n onClick: () => void;\n}> = ({ product, onClick }) => {\n const badge =\n STATUS_BADGE_STYLES[product.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {product.icon ? (\n <img\n src={product.icon}\n alt={product.name}\n className=\"h-10 w-10 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary shrink-0\">\n <Package size={20} />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">\n {product.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {formatPrice(product.price, product.currency)}\n </p>\n </div>\n <span\n className={`shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <Package size={12} className=\"text-text-muted shrink-0\" />\n <span\n className={\n product.stockQuantity === 0\n ? \"text-status-error-text\"\n : \"text-text-secondary\"\n }\n >\n {product.stockQuantity === 0\n ? \"Out of stock\"\n : `${product.stockQuantity} in stock`}\n </span>\n </div>\n </GlassCard>\n );\n};\n\nconst OrgCard: FC<{ org: OrgSummary; onClick: () => void }> = ({\n org,\n onClick,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const initials = org.name\n .split(\" \")\n .map((w: string) => w[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary text-sm font-bold\">\n {initials}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">{org.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {org.publishedApps ?? 0}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n <ArrowRight size={15} className=\"text-text-muted shrink-0\" />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <Download size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.installs\", \"Installs\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n {(org.totalInstalls ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <DollarSign size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.revenue\", \"Revenue\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n ${(org.totalRevenue ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerItemsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SellerItemsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAuKhC,eAAO,MAAM,eAAe,EAAE,EAkhB7B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerItemsPage.js","names":[],"sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n Package,\n Loader2,\n AlertCircle,\n Edit,\n Tag,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n \"DRAFT\" | \"PENDING_REVIEW\" | \"PUBLISHED\" | \"REJECTED\" | \"INACTIVE\";\n\ntype PhysicalProductType =\n | \"MERCHANDISE\"\n | \"HARDWARE\"\n | \"ACCESSORY\"\n | \"BOOK\"\n | \"EQUIPMENT\"\n | \"APPAREL\"\n | \"COLLECTIBLE\"\n | \"CONSUMABLE\";\n\ninterface PhysicalProductDetail {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n sku?: string | null;\n brand?: string | null;\n icon?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Digital product types to exclude even if `nature: PHYSICAL` is set wrong.\n */\nconst DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n\nconst PRODUCT_TYPE_LABELS: Record<PhysicalProductType, string> = {\n MERCHANDISE: \"Merchandise\",\n HARDWARE: \"Hardware\",\n ACCESSORY: \"Accessory\",\n BOOK: \"Book\",\n EQUIPMENT: \"Equipment\",\n APPAREL: \"Apparel\",\n COLLECTIBLE: \"Collectible\",\n CONSUMABLE: \"Consumable\",\n};\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\nconst STATUS_OPTIONS: { value: ProductStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Status\" },\n { value: \"DRAFT\", label: \"Draft\" },\n { value: \"PENDING_REVIEW\", label: \"Pending Review\" },\n { value: \"PUBLISHED\", label: \"Published\" },\n { value: \"REJECTED\", label: \"Rejected\" },\n { value: \"INACTIVE\", label: \"Inactive\" },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\nfunction resolveTypeLabel(type: string): string {\n return (\n PRODUCT_TYPE_LABELS[type as PhysicalProductType] ?? type.replace(/_/g, \" \")\n );\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst SELLER_PUBLISHER_QUERY = `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_ITEMS_QUERY = `\n query SellerItems($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 100, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n sku\n brand\n icon\n createdAt\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerItemsPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [publisherLoading, setPublisherLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProductDetail[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<ProductStatus | \"\">(\"\");\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Fetch publisher first\n useEffect(() => {\n let cancelled = false;\n setPublisherLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: SELLER_PUBLISHER_QUERY }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: { myPublisher?: { id: string } | null };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } catch (err) {\n if (!cancelled) {\n setError(\n err instanceof Error ? err.message : \"Failed to load publisher\",\n );\n }\n } finally {\n if (!cancelled) setPublisherLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken, retryToken]);\n\n // Fetch products once publisher is resolved\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_ITEMS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProductDetail, \"status\"> & { status: string }\n >;\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setProducts(\n (json.data?.products ?? []).map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err.message : \"Failed to load items\");\n }\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken, retryToken]);\n\n // Exclude digital types defensively, then apply client-side filters\n const physicalItems = useMemo(\n () => products.filter((p) => !DIGITAL_TYPES.has(p.type)),\n [products],\n );\n\n const filteredItems = useMemo(\n () =>\n physicalItems.filter((item) => {\n const matchesSearch =\n !searchQuery ||\n item.name.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || item.status === statusFilter;\n return matchesSearch && matchesStatus;\n }),\n [physicalItems, searchQuery, statusFilter],\n );\n\n const stats = useMemo(\n () => ({\n total: physicalItems.length,\n published: physicalItems.filter((i) => i.status === \"PUBLISHED\").length,\n pendingReview: physicalItems.filter((i) => i.status === \"PENDING_REVIEW\")\n .length,\n outOfStock: physicalItems.filter((i) => i.stockQuantity === 0).length,\n }),\n [physicalItems],\n );\n\n const isInitialLoading = publisherLoading && products.length === 0 && !error;\n\n // Full-page loading state\n if (isInitialLoading) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Full-page error state\n if (error && products.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadItems\", \"Failed to load items\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{error}</p>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const hasActiveFilters = Boolean(searchQuery || statusFilter);\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n >\n {/* Summary stats */}\n <EmphasisPanel className=\"mb-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalItems\", \"Total Items\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.total\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.published\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-warning-text\">\n {tr(\"devportal.pendingReview\", \"Pending Review\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.pendingReview\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-error-text\">\n {tr(\"devportal.outOfStock\", \"Out of Stock\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.outOfStock\n )}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Filters + view toggle */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-3\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\"devportal.searchItems\", \"Search items...\")}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface pl-9 pr-4 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as ProductStatus | \"\")\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View toggle */}\n <div className=\"flex items-center gap-1 rounded-md border border-border-seam p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"grid\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.gridView\", \"Grid view\")}\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"list\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.listView\", \"List view\")}\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Results count */}\n {physicalItems.length > 0 && (\n <p className=\"text-sm text-text-secondary\">\n {filteredItems.length} item\n {filteredItems.length !== 1 ? \"s\" : \"\"} found\n </p>\n )}\n\n {/* Content */}\n {!publisherId && !publisherLoading ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Package size={32} />}\n title={tr(\n \"devportal.noPublisherProfileYet\",\n \"No publisher profile yet\",\n )}\n description={tr(\n \"devportal.listAnItemToGetStarted\",\n \"List an item to get started. Your publisher profile will be created automatically.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n ) : productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n ) : filteredItems.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredItems.map((item) => (\n <ProductItemCard\n key={item.id}\n item={item}\n onEdit={() => handleNavigate(`/seller/items/${item.id}`)}\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Item\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Price\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Stock\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredItems.map((item) => {\n const badge =\n STATUS_BADGE_STYLES[item.status] ??\n STATUS_BADGE_STYLES.DRAFT;\n return (\n <tr\n key={item.id}\n className=\"border-b border-border-subtle last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-8 w-8 rounded-lg object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken shrink-0\">\n <Package\n size={14}\n className=\"text-text-muted\"\n />\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">\n {item.name}\n </p>\n {item.sku && (\n <p className=\"text-xs text-text-muted\">\n SKU: {item.sku}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </td>\n <td className=\"px-4 py-3 text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </td>\n <td className=\"px-4 py-3\">\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">\n Out of stock\n </span>\n ) : (\n <span className=\"text-text-primary tabular-nums\">\n {item.stockQuantity}\n </span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() =>\n handleNavigate(`/seller/items/${item.id}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n {tr(\"devportal.edit\", \"Edit\")}\n </button>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )\n ) : physicalItems.length > 0 && hasActiveFilters ? (\n <EmptyState\n illustration=\"empty-search\"\n title={tr(\n \"devportal.noItemsMatchYourSearch\",\n \"No items match your search\",\n )}\n description={tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your search or filters\",\n )}\n />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={tr(\"devportal.noItemsYet\", \"No items yet\")}\n description={tr(\n \"devportal.addYourFirstPhysicalProduct\",\n \"Add your first physical product listing to start selling\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst ProductItemCard: FC<{\n item: PhysicalProductDetail;\n onEdit: () => void;\n}> = ({ item, onEdit }) => {\n const badge = STATUS_BADGE_STYLES[item.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard treatment=\"insight\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-20 w-20 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-20 w-20 items-center justify-center rounded-xl bg-bg-sunken shrink-0\">\n <Package size={32} className=\"text-text-muted\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary truncate\">\n {item.name}\n </p>\n {item.brand && (\n <p className=\"text-xs text-text-muted mt-0.5\">{item.brand}</p>\n )}\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPrice(item.price, item.currency)}\n </p>\n <div className=\"flex items-center gap-2 mt-2 flex-wrap\">\n <span\n className={`rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n <span className=\"rounded-full px-2 py-0.5 text-xs font-medium bg-bg-muted text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-subtle\">\n <div className=\"flex items-center gap-1.5 text-sm\">\n <Tag size={12} className=\"text-text-muted shrink-0\" />\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">Out of stock</span>\n ) : (\n <span className=\"text-text-secondary\">\n {item.stockQuantity} in stock\n </span>\n )}\n </div>\n <button\n onClick={onEdit}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n Edit\n </button>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerItemsPage.js","names":[],"sources":["../../../src/devportal/pages/SellerItemsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Grid,\n List,\n Package,\n Loader2,\n AlertCircle,\n Edit,\n Tag,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n | \"DRAFT\"\n | \"PENDING_REVIEW\"\n | \"PUBLISHED\"\n | \"REJECTED\"\n | \"INACTIVE\";\n\ntype PhysicalProductType =\n | \"MERCHANDISE\"\n | \"HARDWARE\"\n | \"ACCESSORY\"\n | \"BOOK\"\n | \"EQUIPMENT\"\n | \"APPAREL\"\n | \"COLLECTIBLE\"\n | \"CONSUMABLE\";\n\ninterface PhysicalProductDetail {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n sku?: string | null;\n brand?: string | null;\n icon?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Digital product types to exclude even if `nature: PHYSICAL` is set wrong.\n */\nconst DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n\nconst PRODUCT_TYPE_LABELS: Record<PhysicalProductType, string> = {\n MERCHANDISE: \"Merchandise\",\n HARDWARE: \"Hardware\",\n ACCESSORY: \"Accessory\",\n BOOK: \"Book\",\n EQUIPMENT: \"Equipment\",\n APPAREL: \"Apparel\",\n COLLECTIBLE: \"Collectible\",\n CONSUMABLE: \"Consumable\",\n};\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\nconst STATUS_OPTIONS: { value: ProductStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Status\" },\n { value: \"DRAFT\", label: \"Draft\" },\n { value: \"PENDING_REVIEW\", label: \"Pending Review\" },\n { value: \"PUBLISHED\", label: \"Published\" },\n { value: \"REJECTED\", label: \"Rejected\" },\n { value: \"INACTIVE\", label: \"Inactive\" },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\nfunction resolveTypeLabel(type: string): string {\n return (\n PRODUCT_TYPE_LABELS[type as PhysicalProductType] ?? type.replace(/_/g, \" \")\n );\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst SELLER_PUBLISHER_QUERY = `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_ITEMS_QUERY = `\n query SellerItems($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 100, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n sku\n brand\n icon\n createdAt\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerItemsPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [publisherLoading, setPublisherLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProductDetail[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<ProductStatus | \"\">(\"\");\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Fetch publisher first\n useEffect(() => {\n let cancelled = false;\n setPublisherLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: SELLER_PUBLISHER_QUERY }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: { myPublisher?: { id: string } | null };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } catch (err) {\n if (!cancelled) {\n setError(\n err instanceof Error ? err.message : \"Failed to load publisher\",\n );\n }\n } finally {\n if (!cancelled) setPublisherLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken, retryToken]);\n\n // Fetch products once publisher is resolved\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_ITEMS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n if (!res.ok) {\n throw new Error(`Request failed with status ${res.status}`);\n }\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProductDetail, \"status\"> & { status: string }\n >;\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"Unknown GraphQL error\");\n }\n setProducts(\n (json.data?.products ?? []).map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err.message : \"Failed to load items\");\n }\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken, retryToken]);\n\n // Exclude digital types defensively, then apply client-side filters\n const physicalItems = useMemo(\n () => products.filter((p) => !DIGITAL_TYPES.has(p.type)),\n [products],\n );\n\n const filteredItems = useMemo(\n () =>\n physicalItems.filter((item) => {\n const matchesSearch =\n !searchQuery ||\n item.name.toLowerCase().includes(searchQuery.toLowerCase());\n const matchesStatus = !statusFilter || item.status === statusFilter;\n return matchesSearch && matchesStatus;\n }),\n [physicalItems, searchQuery, statusFilter],\n );\n\n const stats = useMemo(\n () => ({\n total: physicalItems.length,\n published: physicalItems.filter((i) => i.status === \"PUBLISHED\").length,\n pendingReview: physicalItems.filter((i) => i.status === \"PENDING_REVIEW\")\n .length,\n outOfStock: physicalItems.filter((i) => i.stockQuantity === 0).length,\n }),\n [physicalItems],\n );\n\n const isInitialLoading = publisherLoading && products.length === 0 && !error;\n\n // Full-page loading state\n if (isInitialLoading) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Full-page error state\n if (error && products.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadItems\", \"Failed to load items\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{error}</p>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const hasActiveFilters = Boolean(searchQuery || statusFilter);\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.myItems\", \"My Items\")}\n description={tr(\n \"devportal.manageYourPhysicalProductListings\",\n \"Manage your physical product listings.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n >\n {/* Summary stats */}\n <EmphasisPanel className=\"mb-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalItems\", \"Total Items\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.total\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr(\"devportal.published\", \"Published\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.published\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-warning-text\">\n {tr(\"devportal.pendingReview\", \"Pending Review\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.pendingReview\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-status-error-text\">\n {tr(\"devportal.outOfStock\", \"Out of Stock\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {productsLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin text-text-secondary\" />\n ) : (\n stats.outOfStock\n )}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Filters + view toggle */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-3\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\"devportal.searchItems\", \"Search items...\")}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface pl-9 pr-4 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as ProductStatus | \"\")\n }\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* View toggle */}\n <div className=\"flex items-center gap-1 rounded-md border border-border-seam p-1\">\n <button\n onClick={() => setViewMode(\"grid\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"grid\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.gridView\", \"Grid view\")}\n >\n <Grid size={16} />\n </button>\n <button\n onClick={() => setViewMode(\"list\")}\n className={`rounded p-1.5 transition-colors ${\n viewMode === \"list\"\n ? \"bg-bg-sunken text-text-primary\"\n : \"text-text-secondary hover:bg-bg-sunken/50\"\n }`}\n title={tr(\"devportal.listView\", \"List view\")}\n >\n <List size={16} />\n </button>\n </div>\n </div>\n\n {/* Results count */}\n {physicalItems.length > 0 && (\n <p className=\"text-sm text-text-secondary\">\n {filteredItems.length} item\n {filteredItems.length !== 1 ? \"s\" : \"\"} found\n </p>\n )}\n\n {/* Content */}\n {!publisherId && !publisherLoading ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Package size={32} />}\n title={tr(\n \"devportal.noPublisherProfileYet\",\n \"No publisher profile yet\",\n )}\n description={tr(\n \"devportal.listAnItemToGetStarted\",\n \"List an item to get started. Your publisher profile will be created automatically.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n ) : productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingItems\", \"Loading items...\")}\n </span>\n </div>\n ) : filteredItems.length > 0 ? (\n viewMode === \"grid\" ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {filteredItems.map((item) => (\n <ProductItemCard\n key={item.id}\n item={item}\n onEdit={() => handleNavigate(`/seller/items/${item.id}`)}\n />\n ))}\n </div>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border border-border-seam\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken\">\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Item\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Type\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Price\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Stock\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody>\n {filteredItems.map((item) => {\n const badge =\n STATUS_BADGE_STYLES[item.status] ??\n STATUS_BADGE_STYLES.DRAFT;\n return (\n <tr\n key={item.id}\n className=\"border-b border-border-subtle last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-8 w-8 rounded-lg object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-bg-sunken shrink-0\">\n <Package\n size={14}\n className=\"text-text-muted\"\n />\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">\n {item.name}\n </p>\n {item.sku && (\n <p className=\"text-xs text-text-muted\">\n SKU: {item.sku}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </td>\n <td className=\"px-4 py-3 text-text-primary tabular-nums\">\n {formatPrice(item.price, item.currency)}\n </td>\n <td className=\"px-4 py-3\">\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">\n Out of stock\n </span>\n ) : (\n <span className=\"text-text-primary tabular-nums\">\n {item.stockQuantity}\n </span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <button\n onClick={() =>\n handleNavigate(`/seller/items/${item.id}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n {tr(\"devportal.edit\", \"Edit\")}\n </button>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )\n ) : physicalItems.length > 0 && hasActiveFilters ? (\n <EmptyState\n illustration=\"empty-search\"\n title={tr(\n \"devportal.noItemsMatchYourSearch\",\n \"No items match your search\",\n )}\n description={tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your search or filters\",\n )}\n />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={tr(\"devportal.noItemsYet\", \"No items yet\")}\n description={tr(\n \"devportal.addYourFirstPhysicalProduct\",\n \"Add your first physical product listing to start selling\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst ProductItemCard: FC<{\n item: PhysicalProductDetail;\n onEdit: () => void;\n}> = ({ item, onEdit }) => {\n const badge = STATUS_BADGE_STYLES[item.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard treatment=\"insight\" className=\"flex flex-col gap-3 p-4\">\n <div className=\"flex items-start gap-3\">\n {item.icon ? (\n <img\n src={item.icon}\n alt={item.name}\n className=\"h-20 w-20 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-20 w-20 items-center justify-center rounded-xl bg-bg-sunken shrink-0\">\n <Package size={32} className=\"text-text-muted\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold text-text-primary truncate\">\n {item.name}\n </p>\n {item.brand && (\n <p className=\"text-xs text-text-muted mt-0.5\">{item.brand}</p>\n )}\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPrice(item.price, item.currency)}\n </p>\n <div className=\"flex items-center gap-2 mt-2 flex-wrap\">\n <span\n className={`rounded-full px-2 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n <span className=\"rounded-full px-2 py-0.5 text-xs font-medium bg-bg-muted text-text-secondary\">\n {resolveTypeLabel(item.type)}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-subtle\">\n <div className=\"flex items-center gap-1.5 text-sm\">\n <Tag size={12} className=\"text-text-muted shrink-0\" />\n {item.stockQuantity === 0 ? (\n <span className=\"text-status-error-text\">Out of stock</span>\n ) : (\n <span className=\"text-text-secondary\">\n {item.stockQuantity} in stock\n </span>\n )}\n </div>\n <button\n onClick={onEdit}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Edit size={12} />\n Edit\n </button>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|