@burdenoff/microfe-devportal 2026.902.1 → 2026.914.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/hooks/useSellerOrganization.d.ts +18 -0
- package/dist/devportal/hooks/useSellerOrganization.d.ts.map +1 -0
- package/dist/devportal/hooks/useSellerOrganization.js +58 -0
- package/dist/devportal/hooks/useSellerOrganization.js.map +1 -0
- package/dist/devportal/pages/CreatePhysicalItemPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreatePhysicalItemPage.js +182 -174
- package/dist/devportal/pages/CreatePhysicalItemPage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js +2 -2
- package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerOrdersPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerOrdersPage.js +2 -2
- package/dist/devportal/pages/SellerOrdersPage.js.map +1 -1
- package/dist/devportal/providers/DevportalProvider.d.ts +8 -0
- package/dist/devportal/providers/DevportalProvider.d.ts.map +1 -1
- package/dist/devportal/providers/DevportalProvider.js +44 -31
- package/dist/devportal/providers/DevportalProvider.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreatePhysicalItemPage.js","names":[],"sources":["../../../src/devportal/pages/CreatePhysicalItemPage.tsx"],"sourcesContent":["import type { FC, ChangeEvent, DragEvent } from \"react\";\nimport { useState, useCallback, useRef } from \"react\";\nimport { Loader2, Upload, X, Info } from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n CheckboxField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { cn } from \"../utils/cn\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ninterface ImagePreview {\n file: File;\n url: string;\n}\n\ninterface FormState {\n name: string;\n slug: string;\n type: string;\n category: string;\n shortDescription: string;\n longDescription: string;\n price: string;\n currency: string;\n tags: string;\n stockQuantity: string;\n sku: string;\n trackInventory: boolean;\n requiresShipping: boolean;\n weightKg: string;\n brand: string;\n countryOfOrigin: string;\n warrantyInfo: string;\n returnPolicy: string;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst PRODUCT_CATEGORIES = [\n { value: \"MERCHANDISE\", label: \"Merchandise\" },\n { value: \"HARDWARE\", label: \"Hardware\" },\n { value: \"ACCESSORY\", label: \"Accessory\" },\n { value: \"BOOK\", label: \"Book\" },\n { value: \"EQUIPMENT\", label: \"Equipment\" },\n { value: \"APPAREL\", label: \"Apparel\" },\n { value: \"COLLECTIBLE\", label: \"Collectible\" },\n { value: \"CONSUMABLE\", label: \"Consumable\" },\n];\n\n// Store category (the product `category` string, distinct from the ProductType above).\n// The five `agri-` slugs are the HealthyBowl Agri-Inputs Store discriminator: picking one lists\n// the item in that farmer-facing storefront (roadmap #26). Leave blank for general listings.\nconst STORE_CATEGORIES = [\n { value: \"agri-seeds\", label: \"Agri-Inputs — Seeds\" },\n { value: \"agri-fertilizer\", label: \"Agri-Inputs — Organic Fertilizer\" },\n { value: \"agri-biopesticide\", label: \"Agri-Inputs — Bio-Pesticides\" },\n { value: \"agri-tools\", label: \"Agri-Inputs — Tools\" },\n { value: \"agri-irrigation\", label: \"Agri-Inputs — Irrigation\" },\n];\n\nconst CURRENCIES = [\n { value: \"USD\", label: \"USD — US Dollar\" },\n { value: \"INR\", label: \"INR — Indian Rupee\" },\n { value: \"EUR\", label: \"EUR — Euro\" },\n { value: \"GBP\", label: \"GBP — British Pound\" },\n { value: \"SGD\", label: \"SGD — Singapore Dollar\" },\n { value: \"AED\", label: \"AED — UAE Dirham\" },\n];\n\nconst REQUIRED_FIELDS: (keyof FormState)[] = [\n \"name\",\n \"slug\",\n \"type\",\n \"shortDescription\",\n \"price\",\n \"currency\",\n \"stockQuantity\",\n];\n\n// ─── GraphQL strings ──────────────────────────────────────────────────────────\n\nconst MY_PUBLISHER_QUERY = /* GraphQL */ `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst ORG_QUERY = /* GraphQL */ `\n query SellerOrgForPublisher {\n developerOrganizations(limit: 1, offset: 0) {\n edges {\n node {\n id\n name\n contactEmail\n websiteUrl\n legalEntityName\n taxCountry\n }\n }\n }\n }\n`;\n\nconst REGISTER_PUBLISHER_MUTATION = /* GraphQL */ `\n mutation RegisterPublisher($input: RegisterPublisherInput!) {\n registerPublisher(input: $input) {\n id\n name\n email\n }\n }\n`;\n\nconst CREATE_PHYSICAL_PRODUCT_MUTATION = /* GraphQL */ `\n mutation CreatePhysicalProduct($input: CreatePhysicalProductInput!) {\n createPhysicalProduct(input: $input) {\n id\n name\n slug\n status\n nature\n type\n price\n currency\n }\n }\n`;\n\nconst SUBMIT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation SubmitProduct($id: ID!, $notes: String) {\n submitProduct(id: $id, notes: $notes) {\n id\n status\n }\n }\n`;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction deriveSlug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nfunction isFormValid(form: FormState): boolean {\n return REQUIRED_FIELDS.every((field) => {\n const value = form[field];\n if (typeof value === \"boolean\") return true;\n return value.trim() !== \"\";\n });\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport const CreatePhysicalItemPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n\n const [form, setForm] = useState<FormState>({\n name: \"\",\n slug: \"\",\n type: \"\",\n category: \"\",\n shortDescription: \"\",\n longDescription: \"\",\n price: \"\",\n currency: \"\",\n tags: \"\",\n stockQuantity: \"1\",\n sku: \"\",\n trackInventory: true,\n requiresShipping: true,\n weightKg: \"\",\n brand: \"\",\n countryOfOrigin: \"\",\n warrantyInfo: \"\",\n returnPolicy: \"\",\n });\n\n const [images, setImages] = useState<ImagePreview[]>([]);\n const [isDragOver, setIsDragOver] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const [submitting, setSubmitting] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [submitSuccess, setSubmitSuccess] = useState(false);\n\n const isValid = isFormValid(form);\n const endpoint = `${apiGatewayUrl || \"\"}/global/graphql`;\n\n // ─── Field helpers ────────────────────────────────────────────────────────\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n };\n\n const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n slug: deriveSlug(name),\n }));\n };\n\n const handleCheckboxChange =\n (key: \"trackInventory\" | \"requiresShipping\") =>\n (e: ChangeEvent<HTMLInputElement>) => {\n setField(key, e.target.checked);\n };\n\n // ─── Image handling ───────────────────────────────────────────────────────\n\n const addFiles = useCallback((files: FileList | File[]) => {\n const incoming = Array.from(files).filter((f) =>\n f.type.startsWith(\"image/\"),\n );\n const previews: ImagePreview[] = incoming.map((file) => ({\n file,\n url: URL.createObjectURL(file),\n }));\n setImages((prev) => [...prev, ...previews]);\n }, []);\n\n const handleFileInput = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files) addFiles(e.target.files);\n e.target.value = \"\";\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragOver(false);\n if (e.dataTransfer.files) addFiles(e.dataTransfer.files);\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragOver(true);\n };\n\n const handleDragLeave = () => setIsDragOver(false);\n\n const removeImage = (index: number) => {\n setImages((prev) => {\n const next = [...prev];\n URL.revokeObjectURL(next[index]!.url);\n next.splice(index, 1);\n return next;\n });\n };\n\n // ─── GraphQL fetch helper ─────────────────────────────────────────────────\n\n async function gqlFetch<T = unknown>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<T> {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n const json = (await res.json()) as {\n data?: T;\n errors?: { message: string }[];\n };\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n return json.data as T;\n }\n\n // ─── Submit flow ──────────────────────────────────────────────────────────\n\n const handleSubmit = async () => {\n if (!isValid || submitting) return;\n setSubmitting(true);\n setSubmitError(null);\n\n try {\n // Step 1: check if publisher exists\n const publisherData = await gqlFetch<{\n myPublisher: { id: string } | null;\n }>(MY_PUBLISHER_QUERY);\n\n let publisherId = publisherData.myPublisher?.id ?? null;\n\n // Step 2: resolve the seller's org — needed both to auto-register the\n // publisher (first listing) and to link every physical product back to\n // its seller org, the same way devportal's digital-app publish flow\n // already links `StoreProduct.metadata.developerOrganizationId`.\n const orgData = await gqlFetch<{\n developerOrganizations: {\n edges: {\n node: {\n id: string;\n name: string;\n contactEmail: string | null;\n websiteUrl: string | null;\n legalEntityName: string | null;\n taxCountry: string | null;\n };\n }[];\n };\n }>(ORG_QUERY);\n\n const org = orgData.developerOrganizations.edges[0]?.node ?? null;\n\n if (!publisherId) {\n const registerData = await gqlFetch<{\n registerPublisher: { id: string };\n }>(REGISTER_PUBLISHER_MUTATION, {\n input: {\n name: org?.legalEntityName ?? org?.name ?? \"My Store\",\n email: org?.contactEmail ?? \"seller@burdenoff.com\",\n websiteUrl: org?.websiteUrl ?? null,\n legalName: org?.legalEntityName ?? org?.name ?? \"My Store\",\n country: org?.taxCountry ?? null,\n contactEmail: org?.contactEmail ?? null,\n },\n });\n\n publisherId = registerData.registerPublisher.id;\n }\n\n // Step 3: create the product\n const parsedTags = form.tags\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n\n const productData = await gqlFetch<{\n createPhysicalProduct: { id: string };\n }>(CREATE_PHYSICAL_PRODUCT_MUTATION, {\n input: {\n name: form.name.trim(),\n slug: form.slug.trim(),\n type: form.type,\n category: form.category.trim() || null,\n description: form.shortDescription.trim(),\n longDescription: form.longDescription.trim() || null,\n price: parseFloat(form.price),\n currency: form.currency,\n tags: parsedTags.length > 0 ? parsedTags : null,\n stockQuantity: parseInt(form.stockQuantity, 10),\n sku: form.sku.trim() || null,\n trackInventory: form.trackInventory,\n requiresShipping: form.requiresShipping,\n weight:\n form.requiresShipping && form.weightKg.trim()\n ? parseFloat(form.weightKg)\n : null,\n brand: form.brand.trim() || null,\n countryOfOrigin: form.countryOfOrigin.trim() || null,\n warrantyInfo: form.warrantyInfo.trim() || null,\n returnPolicy: form.returnPolicy.trim() || null,\n metadata: org?.id ? { developerOrganizationId: org.id } : null,\n },\n });\n\n const productId = productData.createPhysicalProduct.id;\n\n // Step 4: submit for review\n await gqlFetch(SUBMIT_PRODUCT_MUTATION, {\n id: productId,\n notes: \"Submitted from seller portal\",\n });\n\n // Step 5: success\n setSubmitSuccess(true);\n setTimeout(() => {\n navigate?.(`${basePath}/seller/items`);\n }, 2000);\n } catch (err) {\n setSubmitError(\n err instanceof Error\n ? err.message\n : \"Submission failed. Please try again.\",\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n return (\n <PageLayout\n title=\"List a Physical Item\"\n description=\"Fill in all required details. Your listing will be reviewed before it appears in the store.\"\n backLink=\"/seller/items\"\n backLabel=\"Back to My Items\"\n >\n <div className=\"space-y-8 max-w-3xl\">\n {/* Success banner */}\n {submitSuccess && (\n <div className=\"rounded-card border border-status-success-text/30 bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n Your listing has been submitted for review! Redirecting...\n </div>\n )}\n\n {/* Error banner */}\n {submitError && (\n <div className=\"rounded-card border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\">\n {submitError}\n </div>\n )}\n\n {/* ── Section 1: Basic Information ── */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details shown in the store listing.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <InputField\n label=\"Name\"\n required\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g. Wireless Ergonomic Keyboard\"\n maxLength={120}\n />\n\n <InputField\n label=\"Slug\"\n required\n value={form.slug}\n onChange={(e) => setField(\"slug\", e.target.value)}\n placeholder=\"auto-derived-from-name\"\n hint=\"URL-friendly identifier. Auto-derived from name; you can edit it.\"\n />\n\n <SelectField\n label=\"Product Type\"\n required\n value={form.type}\n onChange={(e) => setField(\"type\", e.target.value)}\n options={PRODUCT_CATEGORIES}\n placeholder=\"Select a type\"\n />\n\n <SelectField\n label=\"Store Category\"\n value={form.category}\n onChange={(e) => setField(\"category\", e.target.value)}\n options={STORE_CATEGORIES}\n placeholder=\"General / uncategorised\"\n hint=\"Pick an Agri-Inputs category to list this item in the HealthyBowl Agri-Inputs Store. Leave blank for a general store listing.\"\n />\n\n <InputField\n label=\"Short Description\"\n required\n value={form.shortDescription}\n onChange={(e) => setField(\"shortDescription\", e.target.value)}\n placeholder=\"One-line summary shown on the store card\"\n maxLength={160}\n hint={`${form.shortDescription.length}/160`}\n />\n\n <TextareaField\n label=\"Long Description\"\n value={form.longDescription}\n onChange={(e) => setField(\"longDescription\", e.target.value)}\n rows={5}\n placeholder=\"Detailed product description, features, specs…\"\n />\n </div>\n </PageSection>\n\n {/* ── Section 2: Product Images ── */}\n <PageSection\n title=\"Product Images\"\n description=\"The first image is used as the main icon.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-4\">\n {/* Drop zone */}\n <div\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => fileInputRef.current?.click()}\n className={cn(\n \"flex flex-col items-center justify-center gap-3 rounded-input border-2 border-dashed px-6 py-10 cursor-pointer transition-colors\",\n isDragOver\n ? \"border-action-primary-bg bg-action-primary-bg/5\"\n : \"border-border-subtle hover:border-border-strong\",\n )}\n >\n <Upload\n size={28}\n className={\n isDragOver ? \"text-action-primary-bg\" : \"text-text-muted\"\n }\n />\n <div className=\"text-center space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n Drag and drop images here\n </p>\n <p className=\"text-xs text-text-muted\">\n or click to browse — accepts any image format, multiple files\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n className=\"sr-only\"\n onChange={handleFileInput}\n />\n </div>\n\n {/* Previews */}\n {images.length > 0 && (\n <div className=\"flex flex-wrap gap-3\">\n {images.map((img, i) => (\n <div key={img.url} className=\"relative group\">\n <img\n src={img.url}\n alt={`Product image ${i + 1}`}\n className=\"h-20 w-20 rounded-input object-cover border border-border-subtle\"\n />\n {i === 0 && (\n <span className=\"absolute bottom-0 left-0 right-0 rounded-b-input bg-bg-surface/80 text-center text-[10px] font-medium py-0.5 text-text-secondary\">\n Main\n </span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n removeImage(i);\n }}\n className=\"absolute -top-1.5 -right-1.5 hidden group-hover:flex h-5 w-5 items-center justify-center rounded-full bg-bg-surface border border-border-subtle text-text-muted hover:text-status-error-text hover:border-status-error-text/40 transition-colors\"\n aria-label=\"Remove image\"\n >\n <X size={11} />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Tip */}\n <p className=\"flex items-start gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Info size={13} className=\"mt-0.5 shrink-0 text-text-muted\" />\n At least 1 image helps buyers — high quality photos increase\n conversions.\n </p>\n </div>\n </PageSection>\n\n {/* ── Section 3: Pricing ── */}\n <PageSection title=\"Pricing\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Price\"\n required\n type=\"number\"\n value={form.price}\n onChange={(e) => setField(\"price\", e.target.value)}\n placeholder=\"0.00\"\n min={0.01}\n step=\"0.01\"\n />\n\n <SelectField\n label=\"Currency\"\n required\n value={form.currency}\n onChange={(e) => setField(\"currency\", e.target.value)}\n options={CURRENCIES}\n placeholder=\"Select currency\"\n />\n </div>\n\n <InputField\n label=\"Tags\"\n value={form.tags}\n onChange={(e) => setField(\"tags\", e.target.value)}\n placeholder=\"mechanical, wireless, ergonomic\"\n hint=\"Comma-separated keywords to help buyers find your item.\"\n />\n </div>\n </PageSection>\n\n {/* ── Section 4: Inventory ── */}\n <PageSection title=\"Inventory\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Stock Quantity\"\n required\n type=\"number\"\n value={form.stockQuantity}\n onChange={(e) => setField(\"stockQuantity\", e.target.value)}\n min={0}\n step={1}\n />\n\n <InputField\n label=\"SKU\"\n value={form.sku}\n onChange={(e) => setField(\"sku\", e.target.value)}\n placeholder=\"e.g. KB-WL-001\"\n hint=\"Your internal stock-keeping unit reference.\"\n />\n </div>\n\n <CheckboxField\n label=\"Track Inventory\"\n description=\"Automatically reduce stock when orders are placed.\"\n checked={form.trackInventory}\n onChange={handleCheckboxChange(\"trackInventory\")}\n />\n </div>\n </PageSection>\n\n {/* ── Section 5: Shipping ── */}\n <PageSection title=\"Shipping\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <CheckboxField\n label=\"Requires Shipping\"\n description=\"This item will be physically shipped to the buyer.\"\n checked={form.requiresShipping}\n onChange={handleCheckboxChange(\"requiresShipping\")}\n />\n\n {form.requiresShipping && (\n <InputField\n label=\"Weight (kg)\"\n type=\"number\"\n value={form.weightKg}\n onChange={(e) => setField(\"weightKg\", e.target.value)}\n placeholder=\"0.0\"\n min={0}\n step=\"0.01\"\n hint=\"Used to calculate shipping costs.\"\n />\n )}\n\n <p className=\"flex items-start gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Info size={13} className=\"mt-0.5 shrink-0 text-text-muted\" />\n Delivery fees are configured at the organization level in store\n settings.\n </p>\n </div>\n </PageSection>\n\n {/* ── Section 6: Product Details ── */}\n <PageSection\n title=\"Product Details\"\n description=\"Optional supplemental information for buyers.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Brand\"\n value={form.brand}\n onChange={(e) => setField(\"brand\", e.target.value)}\n placeholder=\"e.g. Logitech\"\n />\n\n <InputField\n label=\"Country of Origin\"\n value={form.countryOfOrigin}\n onChange={(e) => setField(\"countryOfOrigin\", e.target.value)}\n placeholder=\"e.g. China\"\n />\n </div>\n\n <InputField\n label=\"Warranty Info\"\n value={form.warrantyInfo}\n onChange={(e) => setField(\"warrantyInfo\", e.target.value)}\n placeholder=\"e.g. 1-year limited manufacturer warranty\"\n />\n\n <TextareaField\n label=\"Return Policy\"\n value={form.returnPolicy}\n onChange={(e) => setField(\"returnPolicy\", e.target.value)}\n rows={3}\n placeholder=\"e.g. 30-day returns accepted\"\n />\n </div>\n </PageSection>\n\n {/* ── Actions ── */}\n <div className=\"flex items-center gap-3 pb-8\">\n <button\n type=\"button\"\n onClick={() => navigate?.(`${basePath}/seller/items`)}\n className=\"rounded-button border border-border-strong px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!isValid || submitting}\n aria-disabled={!isValid || submitting}\n title={\n !isValid ? \"Fill in all required fields to continue\" : undefined\n }\n className={cn(\n \"inline-flex items-center gap-2 rounded-button px-5 py-2 text-sm font-medium transition-colors\",\n \"bg-action-primary-bg text-action-primary-text\",\n \"hover:bg-action-primary-bg/90\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n \"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2\",\n )}\n >\n {submitting ? (\n <>\n <Loader2 size={15} className=\"animate-spin\" />\n Submitting...\n </>\n ) : (\n \"Create & Submit for Review\"\n )}\n </button>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreatePhysicalItemPage.js","names":[],"sources":["../../../src/devportal/pages/CreatePhysicalItemPage.tsx"],"sourcesContent":["import type { FC, ChangeEvent, DragEvent } from \"react\";\nimport { useState, useCallback, useRef } from \"react\";\nimport { Loader2, Upload, X, Info } from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n InputField,\n TextareaField,\n SelectField,\n CheckboxField,\n} from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { useSellerOrganization } from \"../hooks/useSellerOrganization\";\nimport { cn } from \"../utils/cn\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ninterface ImagePreview {\n file: File;\n url: string;\n}\n\ninterface FormState {\n name: string;\n slug: string;\n type: string;\n category: string;\n shortDescription: string;\n longDescription: string;\n price: string;\n currency: string;\n tags: string;\n stockQuantity: string;\n sku: string;\n trackInventory: boolean;\n requiresShipping: boolean;\n weightKg: string;\n brand: string;\n countryOfOrigin: string;\n warrantyInfo: string;\n returnPolicy: string;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst PRODUCT_CATEGORIES = [\n { value: \"MERCHANDISE\", label: \"Merchandise\" },\n { value: \"HARDWARE\", label: \"Hardware\" },\n { value: \"ACCESSORY\", label: \"Accessory\" },\n { value: \"BOOK\", label: \"Book\" },\n { value: \"EQUIPMENT\", label: \"Equipment\" },\n { value: \"APPAREL\", label: \"Apparel\" },\n { value: \"COLLECTIBLE\", label: \"Collectible\" },\n { value: \"CONSUMABLE\", label: \"Consumable\" },\n];\n\n// Store category (the product `category` string, distinct from the ProductType above).\n// The five `agri-` slugs are the HealthyBowl Agri-Inputs Store discriminator: picking one lists\n// the item in that farmer-facing storefront (roadmap #26). Leave blank for general listings.\nconst STORE_CATEGORIES = [\n { value: \"agri-seeds\", label: \"Agri-Inputs — Seeds\" },\n { value: \"agri-fertilizer\", label: \"Agri-Inputs — Organic Fertilizer\" },\n { value: \"agri-biopesticide\", label: \"Agri-Inputs — Bio-Pesticides\" },\n { value: \"agri-tools\", label: \"Agri-Inputs — Tools\" },\n { value: \"agri-irrigation\", label: \"Agri-Inputs — Irrigation\" },\n];\n\nconst CURRENCIES = [\n { value: \"USD\", label: \"USD — US Dollar\" },\n { value: \"INR\", label: \"INR — Indian Rupee\" },\n { value: \"EUR\", label: \"EUR — Euro\" },\n { value: \"GBP\", label: \"GBP — British Pound\" },\n { value: \"SGD\", label: \"SGD — Singapore Dollar\" },\n { value: \"AED\", label: \"AED — UAE Dirham\" },\n];\n\nconst REQUIRED_FIELDS: (keyof FormState)[] = [\n \"name\",\n \"slug\",\n \"type\",\n \"shortDescription\",\n \"price\",\n \"currency\",\n \"stockQuantity\",\n];\n\n// ─── GraphQL strings ──────────────────────────────────────────────────────────\n\nconst MY_PUBLISHER_QUERY = /* GraphQL */ `\n query SellerMyPublisher {\n myPublisher {\n id\n }\n }\n`;\n\nconst REGISTER_PUBLISHER_MUTATION = /* GraphQL */ `\n mutation RegisterPublisher($input: RegisterPublisherInput!) {\n registerPublisher(input: $input) {\n id\n name\n email\n }\n }\n`;\n\nconst CREATE_PHYSICAL_PRODUCT_MUTATION = /* GraphQL */ `\n mutation CreatePhysicalProduct($input: CreatePhysicalProductInput!) {\n createPhysicalProduct(input: $input) {\n id\n name\n slug\n status\n nature\n type\n price\n currency\n }\n }\n`;\n\nconst SUBMIT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation SubmitProduct($id: ID!, $notes: String) {\n submitProduct(id: $id, notes: $notes) {\n id\n status\n }\n }\n`;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction deriveSlug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nfunction isFormValid(form: FormState): boolean {\n return REQUIRED_FIELDS.every((field) => {\n const value = form[field];\n if (typeof value === \"boolean\") return true;\n return value.trim() !== \"\";\n });\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport const CreatePhysicalItemPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = useDevportal();\n const {\n organization: sellerOrganization,\n loading: sellerOrgLoading,\n error: sellerOrgError,\n } = useSellerOrganization();\n\n const [form, setForm] = useState<FormState>({\n name: \"\",\n slug: \"\",\n type: \"\",\n category: \"\",\n shortDescription: \"\",\n longDescription: \"\",\n price: \"\",\n currency: \"\",\n tags: \"\",\n stockQuantity: \"1\",\n sku: \"\",\n trackInventory: true,\n requiresShipping: true,\n weightKg: \"\",\n brand: \"\",\n countryOfOrigin: \"\",\n warrantyInfo: \"\",\n returnPolicy: \"\",\n });\n\n const [images, setImages] = useState<ImagePreview[]>([]);\n const [isDragOver, setIsDragOver] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const [submitting, setSubmitting] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [submitSuccess, setSubmitSuccess] = useState(false);\n\n const isValid = isFormValid(form) && !!sellerOrganization;\n const endpoint = `${apiGatewayUrl || \"\"}/global/graphql`;\n\n // ─── Field helpers ────────────────────────────────────────────────────────\n\n const setField = <K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((prev) => ({ ...prev, [key]: value }));\n };\n\n const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n slug: deriveSlug(name),\n }));\n };\n\n const handleCheckboxChange =\n (key: \"trackInventory\" | \"requiresShipping\") =>\n (e: ChangeEvent<HTMLInputElement>) => {\n setField(key, e.target.checked);\n };\n\n // ─── Image handling ───────────────────────────────────────────────────────\n\n const addFiles = useCallback((files: FileList | File[]) => {\n const incoming = Array.from(files).filter((f) =>\n f.type.startsWith(\"image/\"),\n );\n const previews: ImagePreview[] = incoming.map((file) => ({\n file,\n url: URL.createObjectURL(file),\n }));\n setImages((prev) => [...prev, ...previews]);\n }, []);\n\n const handleFileInput = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files) addFiles(e.target.files);\n e.target.value = \"\";\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragOver(false);\n if (e.dataTransfer.files) addFiles(e.dataTransfer.files);\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragOver(true);\n };\n\n const handleDragLeave = () => setIsDragOver(false);\n\n const removeImage = (index: number) => {\n setImages((prev) => {\n const next = [...prev];\n URL.revokeObjectURL(next[index]!.url);\n next.splice(index, 1);\n return next;\n });\n };\n\n // ─── GraphQL fetch helper ─────────────────────────────────────────────────\n\n async function gqlFetch<T = unknown>(\n query: string,\n variables?: Record<string, unknown>,\n ): Promise<T> {\n const res = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n credentials: \"include\",\n body: JSON.stringify({ query, variables }),\n });\n const json = (await res.json()) as {\n data?: T;\n errors?: { message: string }[];\n };\n if (json.errors?.length) {\n throw new Error(json.errors.map((e) => e.message).join(\"; \"));\n }\n return json.data as T;\n }\n\n // ─── Submit flow ──────────────────────────────────────────────────────────\n\n const handleSubmit = async () => {\n if (!isValid || submitting || !sellerOrganization) return;\n setSubmitting(true);\n setSubmitError(null);\n\n try {\n // Step 1: check if publisher exists\n const publisherData = await gqlFetch<{\n myPublisher: { id: string } | null;\n }>(MY_PUBLISHER_QUERY);\n\n let publisherId = publisherData.myPublisher?.id ?? null;\n\n if (!publisherId) {\n const registerData = await gqlFetch<{\n registerPublisher: { id: string };\n }>(REGISTER_PUBLISHER_MUTATION, {\n input: {\n name: sellerOrganization.legalEntityName ?? sellerOrganization.name,\n email: sellerOrganization.contactEmail ?? \"seller@burdenoff.com\",\n websiteUrl: sellerOrganization.websiteUrl ?? null,\n legalName:\n sellerOrganization.legalEntityName ?? sellerOrganization.name,\n country: sellerOrganization.taxCountry ?? null,\n contactEmail: sellerOrganization.contactEmail ?? null,\n },\n });\n\n publisherId = registerData.registerPublisher.id;\n }\n\n // Step 2: create the product, linked to the seller's PHYSICAL org —\n // validated server-side (createPhysicalProduct rejects any org that\n // isn't PHYSICAL-kind, see DeveloperOrgKind / BOFF-7285).\n const parsedTags = form.tags\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n\n const productData = await gqlFetch<{\n createPhysicalProduct: { id: string };\n }>(CREATE_PHYSICAL_PRODUCT_MUTATION, {\n input: {\n name: form.name.trim(),\n slug: form.slug.trim(),\n type: form.type,\n category: form.category.trim() || null,\n description: form.shortDescription.trim(),\n longDescription: form.longDescription.trim() || null,\n price: parseFloat(form.price),\n currency: form.currency,\n tags: parsedTags.length > 0 ? parsedTags : null,\n stockQuantity: parseInt(form.stockQuantity, 10),\n sku: form.sku.trim() || null,\n trackInventory: form.trackInventory,\n requiresShipping: form.requiresShipping,\n weight:\n form.requiresShipping && form.weightKg.trim()\n ? parseFloat(form.weightKg)\n : null,\n brand: form.brand.trim() || null,\n countryOfOrigin: form.countryOfOrigin.trim() || null,\n warrantyInfo: form.warrantyInfo.trim() || null,\n returnPolicy: form.returnPolicy.trim() || null,\n organizationId: sellerOrganization.id,\n },\n });\n\n const productId = productData.createPhysicalProduct.id;\n\n // Step 3: submit for review\n await gqlFetch(SUBMIT_PRODUCT_MUTATION, {\n id: productId,\n notes: \"Submitted from seller portal\",\n });\n\n // Step 4: success\n setSubmitSuccess(true);\n setTimeout(() => {\n navigate?.(`${basePath}/seller/items`);\n }, 2000);\n } catch (err) {\n setSubmitError(\n err instanceof Error\n ? err.message\n : \"Submission failed. Please try again.\",\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n return (\n <PageLayout\n title=\"List a Physical Item\"\n description=\"Fill in all required details. Your listing will be reviewed before it appears in the store.\"\n backLink=\"/seller/items\"\n backLabel=\"Back to My Items\"\n >\n <div className=\"space-y-8 max-w-3xl\">\n {/* Success banner */}\n {submitSuccess && (\n <div className=\"rounded-card border border-status-success-text/30 bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n Your listing has been submitted for review! Redirecting...\n </div>\n )}\n\n {/* Error banner */}\n {submitError && (\n <div className=\"rounded-card border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\">\n {submitError}\n </div>\n )}\n\n {/* Seller org resolution error — the form can't be submitted without one */}\n {sellerOrgError && (\n <div className=\"rounded-card border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\">\n Couldn't set up your seller organization: {sellerOrgError}\n </div>\n )}\n\n {/* ── Section 1: Basic Information ── */}\n <PageSection\n title=\"Basic Information\"\n description=\"Core details shown in the store listing.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <InputField\n label=\"Name\"\n required\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g. Wireless Ergonomic Keyboard\"\n maxLength={120}\n />\n\n <InputField\n label=\"Slug\"\n required\n value={form.slug}\n onChange={(e) => setField(\"slug\", e.target.value)}\n placeholder=\"auto-derived-from-name\"\n hint=\"URL-friendly identifier. Auto-derived from name; you can edit it.\"\n />\n\n <SelectField\n label=\"Product Type\"\n required\n value={form.type}\n onChange={(e) => setField(\"type\", e.target.value)}\n options={PRODUCT_CATEGORIES}\n placeholder=\"Select a type\"\n />\n\n <SelectField\n label=\"Store Category\"\n value={form.category}\n onChange={(e) => setField(\"category\", e.target.value)}\n options={STORE_CATEGORIES}\n placeholder=\"General / uncategorised\"\n hint=\"Pick an Agri-Inputs category to list this item in the HealthyBowl Agri-Inputs Store. Leave blank for a general store listing.\"\n />\n\n <InputField\n label=\"Short Description\"\n required\n value={form.shortDescription}\n onChange={(e) => setField(\"shortDescription\", e.target.value)}\n placeholder=\"One-line summary shown on the store card\"\n maxLength={160}\n hint={`${form.shortDescription.length}/160`}\n />\n\n <TextareaField\n label=\"Long Description\"\n value={form.longDescription}\n onChange={(e) => setField(\"longDescription\", e.target.value)}\n rows={5}\n placeholder=\"Detailed product description, features, specs…\"\n />\n </div>\n </PageSection>\n\n {/* ── Section 2: Product Images ── */}\n <PageSection\n title=\"Product Images\"\n description=\"The first image is used as the main icon.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-4\">\n {/* Drop zone */}\n <div\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={() => fileInputRef.current?.click()}\n className={cn(\n \"flex flex-col items-center justify-center gap-3 rounded-input border-2 border-dashed px-6 py-10 cursor-pointer transition-colors\",\n isDragOver\n ? \"border-action-primary-bg bg-action-primary-bg/5\"\n : \"border-border-subtle hover:border-border-strong\",\n )}\n >\n <Upload\n size={28}\n className={\n isDragOver ? \"text-action-primary-bg\" : \"text-text-muted\"\n }\n />\n <div className=\"text-center space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n Drag and drop images here\n </p>\n <p className=\"text-xs text-text-muted\">\n or click to browse — accepts any image format, multiple files\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n className=\"sr-only\"\n onChange={handleFileInput}\n />\n </div>\n\n {/* Previews */}\n {images.length > 0 && (\n <div className=\"flex flex-wrap gap-3\">\n {images.map((img, i) => (\n <div key={img.url} className=\"relative group\">\n <img\n src={img.url}\n alt={`Product image ${i + 1}`}\n className=\"h-20 w-20 rounded-input object-cover border border-border-subtle\"\n />\n {i === 0 && (\n <span className=\"absolute bottom-0 left-0 right-0 rounded-b-input bg-bg-surface/80 text-center text-[10px] font-medium py-0.5 text-text-secondary\">\n Main\n </span>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n removeImage(i);\n }}\n className=\"absolute -top-1.5 -right-1.5 hidden group-hover:flex h-5 w-5 items-center justify-center rounded-full bg-bg-surface border border-border-subtle text-text-muted hover:text-status-error-text hover:border-status-error-text/40 transition-colors\"\n aria-label=\"Remove image\"\n >\n <X size={11} />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* Tip */}\n <p className=\"flex items-start gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Info size={13} className=\"mt-0.5 shrink-0 text-text-muted\" />\n At least 1 image helps buyers — high quality photos increase\n conversions.\n </p>\n </div>\n </PageSection>\n\n {/* ── Section 3: Pricing ── */}\n <PageSection title=\"Pricing\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Price\"\n required\n type=\"number\"\n value={form.price}\n onChange={(e) => setField(\"price\", e.target.value)}\n placeholder=\"0.00\"\n min={0.01}\n step=\"0.01\"\n />\n\n <SelectField\n label=\"Currency\"\n required\n value={form.currency}\n onChange={(e) => setField(\"currency\", e.target.value)}\n options={CURRENCIES}\n placeholder=\"Select currency\"\n />\n </div>\n\n <InputField\n label=\"Tags\"\n value={form.tags}\n onChange={(e) => setField(\"tags\", e.target.value)}\n placeholder=\"mechanical, wireless, ergonomic\"\n hint=\"Comma-separated keywords to help buyers find your item.\"\n />\n </div>\n </PageSection>\n\n {/* ── Section 4: Inventory ── */}\n <PageSection title=\"Inventory\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Stock Quantity\"\n required\n type=\"number\"\n value={form.stockQuantity}\n onChange={(e) => setField(\"stockQuantity\", e.target.value)}\n min={0}\n step={1}\n />\n\n <InputField\n label=\"SKU\"\n value={form.sku}\n onChange={(e) => setField(\"sku\", e.target.value)}\n placeholder=\"e.g. KB-WL-001\"\n hint=\"Your internal stock-keeping unit reference.\"\n />\n </div>\n\n <CheckboxField\n label=\"Track Inventory\"\n description=\"Automatically reduce stock when orders are placed.\"\n checked={form.trackInventory}\n onChange={handleCheckboxChange(\"trackInventory\")}\n />\n </div>\n </PageSection>\n\n {/* ── Section 5: Shipping ── */}\n <PageSection title=\"Shipping\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <CheckboxField\n label=\"Requires Shipping\"\n description=\"This item will be physically shipped to the buyer.\"\n checked={form.requiresShipping}\n onChange={handleCheckboxChange(\"requiresShipping\")}\n />\n\n {form.requiresShipping && (\n <InputField\n label=\"Weight (kg)\"\n type=\"number\"\n value={form.weightKg}\n onChange={(e) => setField(\"weightKg\", e.target.value)}\n placeholder=\"0.0\"\n min={0}\n step=\"0.01\"\n hint=\"Used to calculate shipping costs.\"\n />\n )}\n\n <p className=\"flex items-start gap-2 rounded-input border border-border-subtle bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <Info size={13} className=\"mt-0.5 shrink-0 text-text-muted\" />\n Delivery fees are configured at the organization level in store\n settings.\n </p>\n </div>\n </PageSection>\n\n {/* ── Section 6: Product Details ── */}\n <PageSection\n title=\"Product Details\"\n description=\"Optional supplemental information for buyers.\"\n >\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-6 space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <InputField\n label=\"Brand\"\n value={form.brand}\n onChange={(e) => setField(\"brand\", e.target.value)}\n placeholder=\"e.g. Logitech\"\n />\n\n <InputField\n label=\"Country of Origin\"\n value={form.countryOfOrigin}\n onChange={(e) => setField(\"countryOfOrigin\", e.target.value)}\n placeholder=\"e.g. China\"\n />\n </div>\n\n <InputField\n label=\"Warranty Info\"\n value={form.warrantyInfo}\n onChange={(e) => setField(\"warrantyInfo\", e.target.value)}\n placeholder=\"e.g. 1-year limited manufacturer warranty\"\n />\n\n <TextareaField\n label=\"Return Policy\"\n value={form.returnPolicy}\n onChange={(e) => setField(\"returnPolicy\", e.target.value)}\n rows={3}\n placeholder=\"e.g. 30-day returns accepted\"\n />\n </div>\n </PageSection>\n\n {/* ── Actions ── */}\n <div className=\"flex items-center gap-3 pb-8\">\n <button\n type=\"button\"\n onClick={() => navigate?.(`${basePath}/seller/items`)}\n className=\"rounded-button border border-border-strong px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={!isValid || submitting}\n aria-disabled={!isValid || submitting}\n title={\n sellerOrgLoading\n ? \"Setting up your seller organization...\"\n : !isValid\n ? \"Fill in all required fields to continue\"\n : undefined\n }\n className={cn(\n \"inline-flex items-center gap-2 rounded-button px-5 py-2 text-sm font-medium transition-colors\",\n \"bg-action-primary-bg text-action-primary-text\",\n \"hover:bg-action-primary-bg/90\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n \"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2\",\n )}\n >\n {submitting ? (\n <>\n <Loader2 size={15} className=\"animate-spin\" />\n Submitting...\n </>\n ) : sellerOrgLoading ? (\n <>\n <Loader2 size={15} className=\"animate-spin\" />\n Setting up your store...\n </>\n ) : (\n \"Create & Submit for Review\"\n )}\n </button>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -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;AA2I3C,eAAO,MAAM,mBAAmB,EAAE,EA+SjC,CAAC"}
|
|
@@ -40,7 +40,7 @@ function S(e, t) {
|
|
|
40
40
|
maximumFractionDigits: 2
|
|
41
41
|
}).format(e)} ${n}`;
|
|
42
42
|
}
|
|
43
|
-
var C = "\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", w = "\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", T = () => {
|
|
43
|
+
var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n kind\n }\n myPublisher {\n id\n }\n }\n", w = "\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", T = () => {
|
|
44
44
|
let { navigate: u, basePath: d, apiGatewayUrl: y, authToken: x } = e(), { t: S } = v(), T = (e, t) => {
|
|
45
45
|
let n = S(e);
|
|
46
46
|
return n === e ? t : n;
|
|
@@ -60,7 +60,7 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
|
|
|
60
60
|
if (e) return;
|
|
61
61
|
let n = await t.json();
|
|
62
62
|
if (e) return;
|
|
63
|
-
I(n.data?.myDeveloperOrganizations ?? []), R(n.data?.myPublisher?.id ?? null);
|
|
63
|
+
I((n.data?.myDeveloperOrganizations ?? []).filter((e) => e.kind === "PHYSICAL")), R(n.data?.myPublisher?.id ?? null);
|
|
64
64
|
} finally {
|
|
65
65
|
e || M(!1);
|
|
66
66
|
}
|
|
@@ -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\"\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
|
+
{"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 kind: \"DIGITAL\" | \"PHYSICAL\";\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 kind\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 // Seller portal only ever shows PHYSICAL-kind orgs — a DIGITAL org\n // (regular app-publishing) has no business here. See DeveloperOrgKind\n // (BOFF-7285).\n setOrgs(\n (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n ),\n );\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":";;;;;;;;;;;AAuDA,IAAM,IAGF;CACF,WAAW;EACT,OAAO;EACP,WAAW;EACZ;CACD,gBAAgB;EACd,OAAO;EACP,WAAW;EACZ;CACD,OAAO;EACL,OAAO;EACP,WAAW;EACZ;CACD,UAAU;EACR,OAAO;EACP,WAAW;EACZ;CACD,UAAU;EACR,OAAO;EACP,WAAW;EACZ;CACF;AAMD,SAAS,EAAY,GAAe,GAA0B;CAC5D,IAAM,IAAO,KAAY;AAOzB,QAAO,GANW,IAAI,KAAK,aAAa,SAAS;EAC/C,OAAO;EACP,UAAU;EACV,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CAAC,OAAO,EAAM,CACI,GAAG;;AAOzB,IAAM,IAAkB,uQAmBlB,IAAwB,oSAoBjB,UAAgC;CAC3C,IAAM,EAAE,aAAU,aAAU,kBAAe,iBAAc,GAAc,EACjE,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAG9B,IAAS,KAAiB,mBAE1B,CAAC,GAAkB,KAAuB,EAAS,GAAK,EACxD,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAM,KAAW,EAAuB,EAAE,CAAC,EAC5C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAU,KAAe,EAA4B,EAAE,CAAC;AA8C/D,CA3CA,QAAgB;EACd,IAAI,IAAY;AAoChB,SAnCA,EAAoB,GAAK,GAEb,YAAY;AACtB,OAAI;IACF,IAAM,IAAM,MAAM,MAAM,GAAQ;KAC9B,QAAQ;KACR,SAAS;MACP,gBAAgB;MAChB,eAAe,UAAU,KAAa;MACvC;KACD,MAAM,KAAK,UAAU,EAAE,OAAO,GAAiB,CAAC;KACjD,CAAC;AACF,QAAI,EAAW;IACf,IAAM,IAAQ,MAAM,EAAI,MAAM;AAM9B,QAAI,EAAW;AASf,IALA,GACG,EAAK,MAAM,4BAA4B,EAAE,EAAE,QACzC,MAAQ,EAAI,SAAS,WACvB,CACF,EACD,EAAe,EAAK,MAAM,aAAa,MAAM,KAAK;aAC1C;AACR,IAAK,KAAW,EAAoB,GAAM;;MAIpC,QACG;AACX,OAAY;;IAEb,CAAC,GAAQ,EAAU,CAAC,EAGvB,QAAgB;AACd,MAAI,CAAC,EAAa;EAClB,IAAI,IAAY;AAwChB,SAvCA,EAAmB,GAAK,GAEZ,YAAY;AACtB,OAAI;IACF,IAAM,IAAM,MAAM,MAAM,GAAQ;KAC9B,QAAQ;KACR,SAAS;MACP,gBAAgB;MAChB,eAAe,UAAU,KAAa;MACvC;KACD,MAAM,KAAK,UAAU;MACnB,OAAO;MACP,WAAW,EAAE,gBAAa;MAC3B,CAAC;KACH,CAAC;AACF,QAAI,EAAW;IACf,IAAM,IAAQ,MAAM,EAAI,MAAM;AAO9B,QAAI,EAAW;IACf,IAAM,IAAgB,IAAI,IAAI;KAAC;KAAO;KAAQ;KAAW,CAAC;AAC1D,OACG,EAAK,MAAM,YAAY,EAAE,EACvB,QAAQ,MAAM,CAAC,EAAc,IAAI,EAAE,KAAK,CAAC,CACzC,KAAK,OAAO;KACX,GAAG;KACH,QAAQ,EAAE;KACX,EAAE,CACN;aACO;AACR,IAAK,KAAW,EAAmB,GAAM;;MAInC,QACG;AACX,OAAY;;IAEb;EAAC;EAAa;EAAQ;EAAU,CAAC;CAEpC,IAAM,IAAY,GACZ,IAAe,KAAoB,GACnC,IAAU,CAAC,KAAa,EAAK,SAAS,GACtC,IAAe,CAAC,KAAa,MAAgB,MAE7C,IAAa,EAAS,QACtB,IAAe,EAAS,QAC3B,MAAM,EAAE,WAAW,iBACrB,CAAC,QACI,IAAiB,EAAS,QAC7B,MAAM,EAAE,WAAW,YACrB,CAAC,QAEI,KAAkB,MAAiB;AACvC,MAAW,GAAG,IAAW,IAAO;;AAGlC,QACE,kBAAC,GAAD;EAAiB,UAAA;YACf,kBAAC,GAAD;GACE,OACE,kBAAA,GAAA,EAAA,UAAA;IACG,EAAG,oBAAoB,SAAS;IAAE;IACnC,kBAAC,GAAD,EAAA,UACG,EAAG,uBAAuB,YAAY,EAC1B,CAAA;IACd,EAAA,CAAA;GAEL,SACE,IACE,kBAAC,UAAD;IACE,eAAe,EAAe,oBAAoB;IAClD,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,wBAAwB,eAAe,CACpC;QACN,IAQD,OAPF,kBAAC,UAAD;IACE,eAAe,EAAe,qBAAqB;IACnD,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,gCAAgC,sBAAsB,CACnD;;aAIb,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBACZ,IACC,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA,GAEhE,kBAAA,GAAA,EAAA,UAAA;OACE,kBAAC,GAAD;QACE,MAAM,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;QAC3B,OAAO;QACP,OAAO,EAAG,mBAAmB,QAAQ;QACrC,CAAA;OACF,kBAAC,OAAD,EAAK,WAAU,2BAA4B,CAAA;OAC3C,kBAAC,GAAD;QACE,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;QACzB,OAAO;QACP,OAAO,EAAG,2BAA2B,iBAAiB;QACtD,CAAA;OACF,kBAAC,OAAD,EAAK,WAAU,2BAA4B,CAAA;OAC3C,kBAAC,GAAD;QACE,MAAM,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;QAChC,OAAO;QACP,OAAO,EAAG,uBAAuB,YAAY;QAC7C,CAAA;OACF,kBAAC,OAAD,EAAK,WAAU,2BAA4B,CAAA;OAC3C,kBAAC,GAAD;QACE,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;QAC7B,OAAO,EAAK;QACZ,OAAO,EAAG,2BAA2B,gBAAgB;QACrD,CAAA;OACD,EAAA,CAAA;MAED,CAAA;KAGN,kBAAC,GAAD;MACE,OAAO,EAAG,4BAA4B,kBAAkB;MACxD,QACE,EAAS,SAAS,IAChB,kBAAC,UAAD;OACE,eAAe,EAAe,gBAAgB;OAC9C,WAAU;iBAFZ,CAIG,EAAG,qBAAqB,WAAW,EACpC,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA,CACjB;WACP,KAAA;gBAGL,KAAa,IACZ,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;OAC5D,CAAA,GACJ,EAAS,SAAS,IACpB,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MACzB,kBAAC,GAAD;QAEW;QACT,eAAe,EAAe,gBAAgB;QAC9C,EAHK,EAAQ,GAGb,CACF;OACE,CAAA,GAEN,kBAAC,GAAD;OACE,cAAa;OACb,OACG,IAKG,EAAG,+BAA+B,uBAAuB,GAJzD,EACE,sCACA,8BACD;OAGP,aACG,IAKG,EACE,4CACA,gEACD,GAPD,EACE,qCACA,4DACD;OAMP,QACG,IAYC,kBAAC,UAAD;QACE,eAAe,EAAe,oBAAoB;QAClD,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,wBAAwB,eAAe,CACpC;YAjBT,kBAAC,UAAD;QACE,eAAe,EAAe,qBAAqB;QACnD,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EACC,gCACA,sBACD,CACM;;OAWb,CAAA;MAEQ,CAAA;MAGZ,KAAa,EAAK,SAAS,MAC3B,kBAAC,GAAD;MACE,OAAO,EAAG,+BAA+B,qBAAqB;MAC9D,QACE,EAAK,SAAS,IACZ,kBAAC,UAAD;OACE,eAAe,EAAe,iBAAiB;OAC/C,WAAU;iBAFZ,CAIG,EAAG,qBAAqB,WAAW,EACpC,kBAAC,GAAD,EAAY,MAAM,IAAM,CAAA,CACjB;WACP,KAAA;gBAGL,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;OAC5D,CAAA,GAEN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MACrB,kBAAC,GAAD;QAEO;QACL,eAAe,EAAe,kBAAkB,EAAI,KAAK;QACzD,EAHK,EAAI,GAGT,CACF;OACE,CAAA;MAEI,CAAA;KAEZ;;GACK,CAAA;EACG,CAAA;GAQhB,KAAmE,EACvE,SACA,UACA,eAEA,kBAAC,OAAD;CAAK,WAAU;WAAf;EACE,kBAAC,QAAD;GAAM,WAAU;aAAmB;GAAY,CAAA;EAC/C,kBAAC,QAAD;GAAM,WAAU;aAAsC;GAAa,CAAA;EACnE,kBAAC,QAAD;GAAM,WAAU;aAA+B;GAAa,CAAA;EACxD;IAGF,KAIA,EAAE,UAAO,WAAQ,kBACrB,kBAAC,WAAD;CAAS,WAAU;WAAnB,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,MAAD;GAAI,WAAU;aACX;GACE,CAAA,EACJ,EACG;KACL,EACO;IAGN,KAGA,EAAE,YAAS,iBAAc;CAC7B,IAAM,IACJ,EAAoB,EAAQ,WAAW,EAAoB;AAE7D,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACV,WAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAS;;YATf,CAaE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,EAAQ,OACP,kBAAC,OAAD;KACE,KAAK,EAAQ;KACb,KAAK,EAAQ;KACb,WAAU;KACV,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;KACjB,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAQ;MACP,CAAA,EACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAY,EAAQ,OAAO,EAAQ,SAAS;MAC3C,CAAA,CACA;;IACN,kBAAC,QAAD;KACE,WAAW,2DAA2D,EAAM;eAE3E,EAAM;KACF,CAAA;IACH;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAS,MAAM;IAAI,WAAU;IAA6B,CAAA,EAC1D,kBAAC,QAAD;IACE,WACE,EAAQ,kBAAkB,IACtB,2BACA;cAGL,EAAQ,kBAAkB,IACvB,iBACA,GAAG,EAAQ,cAAc;IACxB,CAAA,CACH;KACI;;GAIV,KAAyD,EAC7D,QACA,iBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAG9B,IAAW,EAAI,KAClB,MAAM,IAAI,CACV,KAAK,MAAc,EAAE,GAAG,CACxB,KAAK,GAAG,CACR,MAAM,GAAG,EAAE,CACX,aAAa;AAEhB,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACV,WAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAS;;YATf,CAaE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,EAAI,UACH,kBAAC,OAAD;KACE,KAAK,EAAI;KACT,KAAK,EAAI;KACT,WAAU;KACV,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA4C,EAAI;MAAS,CAAA,EACtE,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAI,iBAAiB;OAAG;OACxB,EAAG,2BAA2B,iBAAiB;OAC9C;QACA;;IACN,kBAAC,GAAD;KAAY,MAAM;KAAI,WAAU;KAA6B,CAAA;IACzD;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAU,MAAM;KAAI,WAAU;KAA6B,CAAA,EAC3D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,sBAAsB,WAAW;KACnC,CAAA,EACJ,kBAAC,KAAD;KAAG,WAAU;gBACT,EAAI,iBAAiB,GAAG,gBAAgB;KACxC,CAAA,CACA,EAAA,CAAA,CACF;OACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAY,MAAM;KAAI,WAAU;KAA6B,CAAA,EAC7D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,qBAAqB,UAAU;KACjC,CAAA,EACJ,kBAAC,KAAD;KAAG,WAAU;eAAb,CAAkD,MAC7C,EAAI,gBAAgB,GAAG,gBAAgB,CACxC;OACA,EAAA,CAAA,CACF;MACF;KACI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerOrdersPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SellerOrdersPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/SellerOrdersPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAkLhC,eAAO,MAAM,gBAAgB,EAAE,EAwb9B,CAAC"}
|
|
@@ -66,7 +66,7 @@ var g = {
|
|
|
66
66
|
value: "CANCELLED",
|
|
67
67
|
label: "Cancelled"
|
|
68
68
|
}
|
|
69
|
-
], v = "\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n }\n }\n", y = "\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n";
|
|
69
|
+
], v = "\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n kind\n }\n }\n", y = "\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n";
|
|
70
70
|
function b(e, t) {
|
|
71
71
|
return new Intl.NumberFormat("en-US", {
|
|
72
72
|
style: "currency",
|
|
@@ -104,7 +104,7 @@ var C = () => {
|
|
|
104
104
|
body: JSON.stringify({ query: v })
|
|
105
105
|
})).json();
|
|
106
106
|
if (e) return;
|
|
107
|
-
let n = t.data?.myDeveloperOrganizations ?? [];
|
|
107
|
+
let n = (t.data?.myDeveloperOrganizations ?? []).filter((e) => e.kind === "PHYSICAL");
|
|
108
108
|
j(n), n.length > 0 && N(n[0].id);
|
|
109
109
|
} catch {} finally {
|
|
110
110
|
e || k(!1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerOrdersPage.js","names":[],"sources":["../../../src/devportal/pages/SellerOrdersPage.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 ShoppingBag,\n Loader2,\n AlertCircle,\n Package,\n ChevronDown,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n}\n\ninterface SellerOrder {\n id: string;\n storeOrderId: string | null;\n storeProductId: string | null;\n grossAmount: number;\n netAmount: number;\n platformFee: number;\n currency: string;\n status: SaleStatus;\n buyerUserId: string;\n purchasedAt: string;\n refundAmount: number | null;\n refundedAt: string | null;\n refundReason: string | null;\n discountAmount: number | null;\n taxAmount: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_STYLES: Record<SaleStatus, { label: string; className: string }> =\n {\n PENDING: {\n label: \"Pending\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n COMPLETED: {\n label: \"Completed\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n REFUNDED: {\n label: \"Refunded\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n PARTIAL_REFUND: {\n label: \"Partial Refund\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DISPUTED: {\n label: \"Disputed\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CHARGEBACK: {\n label: \"Chargeback\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CANCELLED: {\n label: \"Cancelled\",\n className: \"text-text-muted bg-bg-muted\",\n },\n };\n\nconst STATUS_FILTER_OPTIONS: { value: SaleStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Orders\" },\n { value: \"PENDING\", label: \"Pending\" },\n { value: \"COMPLETED\", label: \"Completed\" },\n { value: \"REFUNDED\", label: \"Refunded\" },\n { value: \"PARTIAL_REFUND\", label: \"Partial Refund\" },\n { value: \"DISPUTED\", label: \"Disputed\" },\n { value: \"CANCELLED\", label: \"Cancelled\" },\n];\n\n// ---------------------------------------------------------------------------\n// GraphQL\n// ---------------------------------------------------------------------------\n\nconst ORGS_QUERY = `\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n }\n }\n`;\n\nconst ORDERS_QUERY = `\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatCurrency(amount: number, currency: string): string {\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: currency || \"USD\",\n minimumFractionDigits: 2,\n }).format(amount);\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nfunction shortOrderId(id: string | null, fallback: string): string {\n if (id) return `#${id.slice(0, 8).toUpperCase()}`;\n return `#${fallback.slice(0, 8).toUpperCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport const SellerOrdersPage: FC = () => {\n const { apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [orgsLoading, setOrgsLoading] = useState(true);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n\n const [ordersLoading, setOrdersLoading] = useState(false);\n const [orders, setOrders] = useState<SellerOrder[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [statusFilter, setStatusFilter] = useState<SaleStatus | \"\">(\"\");\n const [page, setPage] = useState(0);\n const PAGE_SIZE = 25;\n\n const headers = useMemo(\n () => ({\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n }),\n [authToken],\n );\n\n // Fetch orgs on mount\n useEffect(() => {\n let cancelled = false;\n setOrgsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORGS_QUERY }),\n });\n const json = (await res.json()) as {\n data?: { myDeveloperOrganizations?: OrgSummary[] };\n };\n if (cancelled) return;\n const fetched = json.data?.myDeveloperOrganizations ?? [];\n setOrgs(fetched);\n if (fetched.length > 0) setSelectedOrgId(fetched[0]!.id);\n } catch {\n // leave orgs empty\n } finally {\n if (!cancelled) setOrgsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, headers]);\n\n // Fetch orders when org/filter/page changes\n useEffect(() => {\n if (!selectedOrgId) return;\n let cancelled = false;\n setOrdersLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const variables: Record<string, unknown> = {\n organizationId: selectedOrgId,\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n };\n if (statusFilter) variables.status = statusFilter;\n\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORDERS_QUERY, variables }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: {\n developerOrgSales?: {\n totalCount: number;\n edges: {\n node: Omit<SellerOrder, \"status\"> & { status: string };\n }[];\n };\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"GraphQL error\");\n }\n const conn = json.data?.developerOrgSales;\n setTotalCount(conn?.totalCount ?? 0);\n // Show all orders; physical-product ones have storeProductId set\n setOrders(\n (conn?.edges ?? []).map((e) => ({\n ...e.node,\n status: e.node.status as SaleStatus,\n })),\n );\n } catch (err) {\n if (!cancelled)\n setError(\n err instanceof Error ? err.message : \"Failed to load orders\",\n );\n } finally {\n if (!cancelled) setOrdersLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [selectedOrgId, statusFilter, page, gqlUrl, headers, retryToken]);\n\n // Physical product orders (storeProductId set) vs digital\n const physicalOrders = useMemo(\n () => orders.filter((o) => o.storeProductId !== null),\n [orders],\n );\n\n const stats = useMemo(() => {\n const all = physicalOrders;\n return {\n total: totalCount,\n pending: all.filter((o) => o.status === \"PENDING\").length,\n completed: all.filter((o) => o.status === \"COMPLETED\").length,\n refunded: all.filter(\n (o) => o.status === \"REFUNDED\" || o.status === \"PARTIAL_REFUND\",\n ).length,\n };\n }, [physicalOrders, totalCount]);\n\n if (orgsLoading) {\n return (\n <PageLayout title={tr(\"devportal.orders\", \"Orders\")}>\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (orgs.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<ShoppingBag size={32} />}\n title={tr(\"devportal.noOrgForOrders\", \"No organization yet\")}\n description={tr(\n \"devportal.createOrgToSell\",\n \"Create a developer organization to start selling physical products and receive orders.\",\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n {/* Controls row */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-6\">\n {/* Org picker (shown only when multiple orgs) */}\n {orgs.length > 1 && (\n <div className=\"relative\">\n <select\n value={selectedOrgId ?? \"\"}\n onChange={(e) => {\n setSelectedOrgId(e.target.value);\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {orgs.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n )}\n\n {/* Status filter */}\n <div className=\"relative\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as SaleStatus | \"\");\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_FILTER_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n </div>\n\n {/* Stats row */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4 mb-6\">\n {[\n { label: \"Total Orders\", value: stats.total },\n {\n label: \"Pending\",\n value: stats.pending,\n color: \"text-status-warning-text\",\n },\n {\n label: \"Completed\",\n value: stats.completed,\n color: \"text-status-success-text\",\n },\n {\n label: \"Refunded\",\n value: stats.refunded,\n color: \"text-status-error-text\",\n },\n ].map((s) => (\n <div\n key={s.label}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-4 py-3\"\n >\n <p className={`text-xs ${s.color ?? \"text-text-secondary\"}`}>\n {s.label}\n </p>\n <p className=\"mt-1 text-xl font-semibold tabular-nums\">\n {ordersLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n s.value\n )}\n </p>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"mb-4 flex items-center gap-3 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle\n size={16}\n className=\"shrink-0 text-status-error-text\"\n />\n <span className=\"text-status-error-text\">{error}</span>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"ml-auto text-xs font-medium text-status-error-text underline\"\n >\n Retry\n </button>\n </div>\n )}\n\n {/* Orders table */}\n {ordersLoading ? (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : physicalOrders.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={\n statusFilter\n ? tr(\n \"devportal.noOrdersForFilter\",\n \"No orders match this filter\",\n )\n : tr(\"devportal.noOrdersYet\", \"No orders yet\")\n }\n description={\n statusFilter\n ? tr(\n \"devportal.tryDifferentFilter\",\n \"Try selecting a different status filter\",\n )\n : tr(\n \"devportal.ordersWillAppear\",\n \"Orders placed for your physical items will appear here once customers make purchases.\",\n )\n }\n />\n ) : (\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 Order\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Product ID\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Buyer\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Gross\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Your Earnings\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-left font-medium text-text-secondary\">\n Date\n </th>\n </tr>\n </thead>\n <tbody>\n {physicalOrders.map((order) => {\n const badge =\n STATUS_STYLES[order.status] ?? STATUS_STYLES.PENDING;\n return (\n <tr\n key={order.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 <span className=\"font-mono text-xs font-semibold text-text-primary\">\n {shortOrderId(order.storeOrderId, order.id)}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {order.storeProductId\n ? `…${order.storeProductId.slice(-8)}`\n : \"—\"}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {`…${order.buyerUserId.slice(-8)}`}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-primary\">\n {formatCurrency(order.grossAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-status-success-text\">\n {formatCurrency(order.netAmount, order.currency)}\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 {order.refundAmount != null &&\n order.refundAmount > 0 && (\n <p className=\"mt-0.5 text-[11px] text-status-error-text\">\n Refunded{\" \"}\n {formatCurrency(\n order.refundAmount,\n order.currency,\n )}\n </p>\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary whitespace-nowrap\">\n {formatDate(order.purchasedAt)}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n {totalCount > PAGE_SIZE && (\n <div className=\"mt-4 flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * PAGE_SIZE + 1}–\n {Math.min((page + 1) * PAGE_SIZE, totalCount)} of {totalCount}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n disabled={page === 0}\n onClick={() => setPage((p) => p - 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <button\n disabled={(page + 1) * PAGE_SIZE >= totalCount}\n onClick={() => setPage((p) => p + 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerOrdersPage.js","names":[],"sources":["../../../src/devportal/pages/SellerOrdersPage.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 ShoppingBag,\n Loader2,\n AlertCircle,\n Package,\n ChevronDown,\n} from \"lucide-react\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype SaleStatus =\n | \"PENDING\"\n | \"COMPLETED\"\n | \"REFUNDED\"\n | \"PARTIAL_REFUND\"\n | \"DISPUTED\"\n | \"CHARGEBACK\"\n | \"CANCELLED\";\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n kind: \"DIGITAL\" | \"PHYSICAL\";\n}\n\ninterface SellerOrder {\n id: string;\n storeOrderId: string | null;\n storeProductId: string | null;\n grossAmount: number;\n netAmount: number;\n platformFee: number;\n currency: string;\n status: SaleStatus;\n buyerUserId: string;\n purchasedAt: string;\n refundAmount: number | null;\n refundedAt: string | null;\n refundReason: string | null;\n discountAmount: number | null;\n taxAmount: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_STYLES: Record<SaleStatus, { label: string; className: string }> =\n {\n PENDING: {\n label: \"Pending\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n COMPLETED: {\n label: \"Completed\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n REFUNDED: {\n label: \"Refunded\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n PARTIAL_REFUND: {\n label: \"Partial Refund\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DISPUTED: {\n label: \"Disputed\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CHARGEBACK: {\n label: \"Chargeback\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n CANCELLED: {\n label: \"Cancelled\",\n className: \"text-text-muted bg-bg-muted\",\n },\n };\n\nconst STATUS_FILTER_OPTIONS: { value: SaleStatus | \"\"; label: string }[] = [\n { value: \"\", label: \"All Orders\" },\n { value: \"PENDING\", label: \"Pending\" },\n { value: \"COMPLETED\", label: \"Completed\" },\n { value: \"REFUNDED\", label: \"Refunded\" },\n { value: \"PARTIAL_REFUND\", label: \"Partial Refund\" },\n { value: \"DISPUTED\", label: \"Disputed\" },\n { value: \"CANCELLED\", label: \"Cancelled\" },\n];\n\n// ---------------------------------------------------------------------------\n// GraphQL\n// ---------------------------------------------------------------------------\n\nconst ORGS_QUERY = `\n query SellerOrdersOrgs {\n myDeveloperOrganizations {\n id\n name\n slug\n kind\n }\n }\n`;\n\nconst ORDERS_QUERY = `\n query SellerOrders($organizationId: ID!, $status: SaleStatus, $limit: Int, $offset: Int) {\n developerOrgSales(\n organizationId: $organizationId\n status: $status\n limit: $limit\n offset: $offset\n ) {\n totalCount\n edges {\n node {\n id\n storeOrderId\n storeProductId\n grossAmount\n netAmount\n platformFee\n currency\n status\n buyerUserId\n purchasedAt\n refundAmount\n refundedAt\n refundReason\n discountAmount\n taxAmount\n }\n }\n pageInfo { hasNextPage hasPreviousPage }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatCurrency(amount: number, currency: string): string {\n return new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: currency || \"USD\",\n minimumFractionDigits: 2,\n }).format(amount);\n}\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nfunction shortOrderId(id: string | null, fallback: string): string {\n if (id) return `#${id.slice(0, 8).toUpperCase()}`;\n return `#${fallback.slice(0, 8).toUpperCase()}`;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport const SellerOrdersPage: FC = () => {\n const { apiGatewayUrl, authToken } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [orgsLoading, setOrgsLoading] = useState(true);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);\n\n const [ordersLoading, setOrdersLoading] = useState(false);\n const [orders, setOrders] = useState<SellerOrder[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n\n const [statusFilter, setStatusFilter] = useState<SaleStatus | \"\">(\"\");\n const [page, setPage] = useState(0);\n const PAGE_SIZE = 25;\n\n const headers = useMemo(\n () => ({\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n }),\n [authToken],\n );\n\n // Fetch orgs on mount\n useEffect(() => {\n let cancelled = false;\n setOrgsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORGS_QUERY }),\n });\n const json = (await res.json()) as {\n data?: { myDeveloperOrganizations?: OrgSummary[] };\n };\n if (cancelled) return;\n // Seller portal only ever shows PHYSICAL-kind orgs — see\n // DeveloperOrgKind (BOFF-7285).\n const fetched = (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n );\n setOrgs(fetched);\n if (fetched.length > 0) setSelectedOrgId(fetched[0]!.id);\n } catch {\n // leave orgs empty\n } finally {\n if (!cancelled) setOrgsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, headers]);\n\n // Fetch orders when org/filter/page changes\n useEffect(() => {\n if (!selectedOrgId) return;\n let cancelled = false;\n setOrdersLoading(true);\n setError(null);\n\n const run = async () => {\n try {\n const variables: Record<string, unknown> = {\n organizationId: selectedOrgId,\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n };\n if (statusFilter) variables.status = statusFilter;\n\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query: ORDERS_QUERY, variables }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = (await res.json()) as {\n data?: {\n developerOrgSales?: {\n totalCount: number;\n edges: {\n node: Omit<SellerOrder, \"status\"> & { status: string };\n }[];\n };\n };\n errors?: { message: string }[];\n };\n if (cancelled) return;\n if (json.errors?.length) {\n throw new Error(json.errors[0]?.message ?? \"GraphQL error\");\n }\n const conn = json.data?.developerOrgSales;\n setTotalCount(conn?.totalCount ?? 0);\n // Show all orders; physical-product ones have storeProductId set\n setOrders(\n (conn?.edges ?? []).map((e) => ({\n ...e.node,\n status: e.node.status as SaleStatus,\n })),\n );\n } catch (err) {\n if (!cancelled)\n setError(\n err instanceof Error ? err.message : \"Failed to load orders\",\n );\n } finally {\n if (!cancelled) setOrdersLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [selectedOrgId, statusFilter, page, gqlUrl, headers, retryToken]);\n\n // Physical product orders (storeProductId set) vs digital\n const physicalOrders = useMemo(\n () => orders.filter((o) => o.storeProductId !== null),\n [orders],\n );\n\n const stats = useMemo(() => {\n const all = physicalOrders;\n return {\n total: totalCount,\n pending: all.filter((o) => o.status === \"PENDING\").length,\n completed: all.filter((o) => o.status === \"COMPLETED\").length,\n refunded: all.filter(\n (o) => o.status === \"REFUNDED\" || o.status === \"PARTIAL_REFUND\",\n ).length,\n };\n }, [physicalOrders, totalCount]);\n\n if (orgsLoading) {\n return (\n <PageLayout title={tr(\"devportal.orders\", \"Orders\")}>\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (orgs.length === 0) {\n return (\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n <EmptyState\n illustration=\"empty-generic\"\n icon={<ShoppingBag size={32} />}\n title={tr(\"devportal.noOrgForOrders\", \"No organization yet\")}\n description={tr(\n \"devportal.createOrgToSell\",\n \"Create a developer organization to start selling physical products and receive orders.\",\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.orders\", \"Orders\")}\n description={tr(\n \"devportal.ordersDesc\",\n \"Orders placed by customers for your physical items.\",\n )}\n >\n {/* Controls row */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-6\">\n {/* Org picker (shown only when multiple orgs) */}\n {orgs.length > 1 && (\n <div className=\"relative\">\n <select\n value={selectedOrgId ?? \"\"}\n onChange={(e) => {\n setSelectedOrgId(e.target.value);\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {orgs.map((org) => (\n <option key={org.id} value={org.id}>\n {org.name}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n )}\n\n {/* Status filter */}\n <div className=\"relative\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as SaleStatus | \"\");\n setPage(0);\n }}\n className=\"appearance-none rounded-md border border-border-seam bg-bg-surface pl-3 pr-8 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {STATUS_FILTER_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <ChevronDown\n size={14}\n className=\"pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-text-muted\"\n />\n </div>\n </div>\n\n {/* Stats row */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4 mb-6\">\n {[\n { label: \"Total Orders\", value: stats.total },\n {\n label: \"Pending\",\n value: stats.pending,\n color: \"text-status-warning-text\",\n },\n {\n label: \"Completed\",\n value: stats.completed,\n color: \"text-status-success-text\",\n },\n {\n label: \"Refunded\",\n value: stats.refunded,\n color: \"text-status-error-text\",\n },\n ].map((s) => (\n <div\n key={s.label}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-4 py-3\"\n >\n <p className={`text-xs ${s.color ?? \"text-text-secondary\"}`}>\n {s.label}\n </p>\n <p className=\"mt-1 text-xl font-semibold tabular-nums\">\n {ordersLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n s.value\n )}\n </p>\n </div>\n ))}\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"mb-4 flex items-center gap-3 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle\n size={16}\n className=\"shrink-0 text-status-error-text\"\n />\n <span className=\"text-status-error-text\">{error}</span>\n <button\n onClick={() => setRetryToken((n) => n + 1)}\n className=\"ml-auto text-xs font-medium text-status-error-text underline\"\n >\n Retry\n </button>\n </div>\n )}\n\n {/* Orders table */}\n {ordersLoading ? (\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : physicalOrders.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Package size={32} />}\n title={\n statusFilter\n ? tr(\n \"devportal.noOrdersForFilter\",\n \"No orders match this filter\",\n )\n : tr(\"devportal.noOrdersYet\", \"No orders yet\")\n }\n description={\n statusFilter\n ? tr(\n \"devportal.tryDifferentFilter\",\n \"Try selecting a different status filter\",\n )\n : tr(\n \"devportal.ordersWillAppear\",\n \"Orders placed for your physical items will appear here once customers make purchases.\",\n )\n }\n />\n ) : (\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 Order\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Product ID\n </th>\n <th className=\"px-4 py-3 text-left font-medium text-text-secondary\">\n Buyer\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Gross\n </th>\n <th className=\"px-4 py-3 text-right font-medium text-text-secondary\">\n Your Earnings\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-left font-medium text-text-secondary\">\n Date\n </th>\n </tr>\n </thead>\n <tbody>\n {physicalOrders.map((order) => {\n const badge =\n STATUS_STYLES[order.status] ?? STATUS_STYLES.PENDING;\n return (\n <tr\n key={order.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 <span className=\"font-mono text-xs font-semibold text-text-primary\">\n {shortOrderId(order.storeOrderId, order.id)}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {order.storeProductId\n ? `…${order.storeProductId.slice(-8)}`\n : \"—\"}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"font-mono text-xs text-text-secondary\">\n {`…${order.buyerUserId.slice(-8)}`}\n </span>\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-primary\">\n {formatCurrency(order.grossAmount, order.currency)}\n </td>\n <td className=\"px-4 py-3 text-right tabular-nums text-status-success-text\">\n {formatCurrency(order.netAmount, order.currency)}\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 {order.refundAmount != null &&\n order.refundAmount > 0 && (\n <p className=\"mt-0.5 text-[11px] text-status-error-text\">\n Refunded{\" \"}\n {formatCurrency(\n order.refundAmount,\n order.currency,\n )}\n </p>\n )}\n </td>\n <td className=\"px-4 py-3 text-xs text-text-secondary whitespace-nowrap\">\n {formatDate(order.purchasedAt)}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n\n {/* Pagination */}\n {totalCount > PAGE_SIZE && (\n <div className=\"mt-4 flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n Showing {page * PAGE_SIZE + 1}–\n {Math.min((page + 1) * PAGE_SIZE, totalCount)} of {totalCount}\n </span>\n <div className=\"flex items-center gap-2\">\n <button\n disabled={page === 0}\n onClick={() => setPage((p) => p - 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Previous\n </button>\n <button\n disabled={(page + 1) * PAGE_SIZE >= totalCount}\n onClick={() => setPage((p) => p + 1)}\n className=\"rounded-md border border-border-seam px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n </button>\n </div>\n </div>\n )}\n </>\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -8,6 +8,14 @@ import { DevportalRootProps } from '../types';
|
|
|
8
8
|
type DevportalContextValue = DevportalRootProps & {
|
|
9
9
|
activeOrgId: string | null;
|
|
10
10
|
setActiveOrgId: (id: string | null) => void;
|
|
11
|
+
/**
|
|
12
|
+
* The seller portal's currently-selected PHYSICAL-kind org. Kept separate
|
|
13
|
+
* from activeOrgId (which the rest of devportal treats as DIGITAL) so
|
|
14
|
+
* switching between the seller portal and the regular devportal never
|
|
15
|
+
* mixes up which kind of org is "active" — see DeveloperOrgKind (BOFF-7285).
|
|
16
|
+
*/
|
|
17
|
+
activePhysicalOrgId: string | null;
|
|
18
|
+
setActivePhysicalOrgId: (id: string | null) => void;
|
|
11
19
|
};
|
|
12
20
|
/**
|
|
13
21
|
* Hook to access Developer Portal context
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevportalProvider.d.ts","sourceRoot":"","sources":["../../../src/devportal/providers/DevportalProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AASnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"DevportalProvider.d.ts","sourceRoot":"","sources":["../../../src/devportal/providers/DevportalProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AASnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAKnD;;;GAGG;AACH,KAAK,qBAAqB,GAAG,kBAAkB,GAAG;IAChD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5C;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,sBAAsB,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACrD,CAAC;AAIF;;GAEG;AACH,eAAO,MAAM,YAAY,6BAMxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAiIvE,CAAC"}
|