@burdenoff/microfe-devportal 2026.716.2 → 2026.720.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devportal/components/PermissionGuard.d.ts +14 -1
- package/dist/devportal/components/PermissionGuard.d.ts.map +1 -1
- package/dist/devportal/components/PermissionGuard.js +16 -13
- package/dist/devportal/components/PermissionGuard.js.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.d.ts.map +1 -1
- package/dist/devportal/hooks/usePackageUpload.js.map +1 -1
- package/dist/devportal/pages/AdminReviewPage.js +9 -9
- package/dist/devportal/pages/ApiKeysPage.js +180 -181
- package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationDetailPage.js +3 -3
- package/dist/devportal/pages/ApplicationVersionsPage.js +4 -4
- package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
- package/dist/devportal/pages/ApplicationsPage.js +4 -4
- package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
- package/dist/devportal/pages/CreateApiKeyPage.js +1 -1
- package/dist/devportal/pages/CreateApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateApplicationPage.js.map +1 -1
- package/dist/devportal/pages/CreateOAuthClientPage.js +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +1 -1
- package/dist/devportal/pages/CreatePhysicalItemPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreatePhysicalItemPage.js +147 -116
- package/dist/devportal/pages/CreatePhysicalItemPage.js.map +1 -1
- package/dist/devportal/pages/CreateWebhookPage.js +25 -25
- package/dist/devportal/pages/DashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/DashboardPage.js +110 -111
- package/dist/devportal/pages/DashboardPage.js.map +1 -1
- package/dist/devportal/pages/DelegationsPage.js +1 -1
- package/dist/devportal/pages/EditApplicationPage.d.ts.map +1 -1
- package/dist/devportal/pages/EditApplicationPage.js +4 -4
- package/dist/devportal/pages/EditApplicationPage.js.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.d.ts.map +1 -1
- package/dist/devportal/pages/ListingPreviewPage.js +160 -161
- package/dist/devportal/pages/ListingPreviewPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.d.ts.map +1 -1
- package/dist/devportal/pages/OAuthClientDetailPage.js +166 -167
- package/dist/devportal/pages/OAuthClientDetailPage.js.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.d.ts.map +1 -1
- package/dist/devportal/pages/OAuthClientsPage.js +121 -122
- package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationDetailPage.js +3 -3
- package/dist/devportal/pages/OrganizationMembersPage.js +4 -4
- package/dist/devportal/pages/OrganizationsPage.js +1 -1
- package/dist/devportal/pages/PublishPage.d.ts.map +1 -1
- package/dist/devportal/pages/PublishPage.js +246 -246
- package/dist/devportal/pages/PublishPage.js.map +1 -1
- package/dist/devportal/pages/PublisherProfilePage.js +4 -4
- package/dist/devportal/pages/PublisherProfilePage.js.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerDashboardPage.js.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.d.ts.map +1 -1
- package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
- package/dist/devportal/pages/StoreSubmissionPage.js +3 -3
- package/dist/devportal/pages/WebhookDetailPage.js +3 -3
- package/dist/devportal/pages/WebhooksPage.js +129 -129
- package/dist/devportal/pages/WebhooksPage.js.map +1 -1
- package/dist/devportal/types/index.d.ts.map +1 -1
- package/dist/devportal/types/index.js.map +1 -1
- package/package.json +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 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\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 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 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: auto-register publisher if needed\n if (!publisherId) {\n const orgData = await gqlFetch<{\n developerOrganizations: {\n edges: {\n node: {\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 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 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 },\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 Category\"\n required\n value={form.type}\n onChange={(e) => setField(\"type\", e.target.value)}\n options={PRODUCT_CATEGORIES}\n placeholder=\"Select a category\"\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 { 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 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: auto-register publisher if needed\n if (!publisherId) {\n const orgData = await gqlFetch<{\n developerOrganizations: {\n edges: {\n node: {\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 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 },\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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"}
|
|
@@ -7,10 +7,10 @@ import { CheckboxField as c, InputField as l, TextareaField as u } from "../comp
|
|
|
7
7
|
import { Alert as d } from "../components/ConfirmDialog.js";
|
|
8
8
|
import { PermissionGuard as f } from "../components/PermissionGuard.js";
|
|
9
9
|
import "../components/index.js";
|
|
10
|
-
import {
|
|
11
|
-
import { useDevportalPermission as m } from "../hooks/usePermission.js";
|
|
10
|
+
import { useDevportalPermission as p } from "../hooks/usePermission.js";
|
|
12
11
|
import "../hooks/index.js";
|
|
13
|
-
import { WEBHOOK_EVENTS as
|
|
12
|
+
import { WEBHOOK_EVENTS as m } from "../types/index.js";
|
|
13
|
+
import { DEVPORTAL_PERMISSIONS as h } from "../constants/permissions.js";
|
|
14
14
|
import { useTr as g } from "../utils/i18n.js";
|
|
15
15
|
import { useEffect as _, useMemo as v, useState as y } from "react";
|
|
16
16
|
import { Loader2 as b, Webhook as x } from "lucide-react";
|
|
@@ -19,18 +19,18 @@ import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
|
19
19
|
import { ShowMore as T } from "@burdenoff/fe-libs/ui";
|
|
20
20
|
//#region src/devportal/pages/CreateWebhookPage.tsx
|
|
21
21
|
var E = {
|
|
22
|
-
Application:
|
|
23
|
-
Installation:
|
|
24
|
-
User:
|
|
25
|
-
Workspace:
|
|
26
|
-
Project:
|
|
27
|
-
Task:
|
|
28
|
-
Comment:
|
|
29
|
-
File:
|
|
30
|
-
Member:
|
|
22
|
+
Application: m.filter((e) => e.startsWith("application.")),
|
|
23
|
+
Installation: m.filter((e) => e.startsWith("installation.")),
|
|
24
|
+
User: m.filter((e) => e.startsWith("user.")),
|
|
25
|
+
Workspace: m.filter((e) => e.startsWith("workspace.")),
|
|
26
|
+
Project: m.filter((e) => e.startsWith("project.")),
|
|
27
|
+
Task: m.filter((e) => e.startsWith("task.")),
|
|
28
|
+
Comment: m.filter((e) => e.startsWith("comment.")),
|
|
29
|
+
File: m.filter((e) => e.startsWith("file.")),
|
|
30
|
+
Member: m.filter((e) => e.startsWith("member."))
|
|
31
31
|
}, D = () => {
|
|
32
|
-
let { appId:
|
|
33
|
-
applicationId:
|
|
32
|
+
let { appId: m } = S(), { navigate: D, basePath: O } = e(), [k, A] = y({
|
|
33
|
+
applicationId: m || "",
|
|
34
34
|
name: "",
|
|
35
35
|
description: "",
|
|
36
36
|
url: "",
|
|
@@ -38,21 +38,21 @@ var E = {
|
|
|
38
38
|
retryEnabled: !0,
|
|
39
39
|
maxRetries: 3
|
|
40
40
|
}), [j, M] = y({}), [N, P] = y(null), { data: F } = r({
|
|
41
|
-
variables: { id:
|
|
42
|
-
skip: !
|
|
41
|
+
variables: { id: m ?? "" },
|
|
42
|
+
skip: !m,
|
|
43
43
|
fetchPolicy: "cache-first"
|
|
44
44
|
});
|
|
45
45
|
_(() => {
|
|
46
46
|
let e = F?.application?.name;
|
|
47
|
-
if (!
|
|
47
|
+
if (!m || !e) return;
|
|
48
48
|
let t = window.__breadcrumbRegistry;
|
|
49
|
-
return t?.register(
|
|
50
|
-
}, [
|
|
49
|
+
return t?.register(m, e), () => t?.unregister(m);
|
|
50
|
+
}, [m, F?.application?.name]);
|
|
51
51
|
let { data: I, loading: L, error: R } = i({
|
|
52
52
|
variables: { limit: 100 },
|
|
53
|
-
skip: !!
|
|
53
|
+
skip: !!m,
|
|
54
54
|
fetchPolicy: "cache-and-network"
|
|
55
|
-
}), [z, { loading: B }] = n(), V = t(), H = g(), U =
|
|
55
|
+
}), [z, { loading: B }] = n(), V = t(), H = g(), U = p(h.WEBHOOK_CREATE), W = v(() => I?.applications?.edges?.map((e) => e.node).map((e) => ({
|
|
56
56
|
id: e.id,
|
|
57
57
|
name: e.name,
|
|
58
58
|
slug: e.slug
|
|
@@ -81,7 +81,7 @@ var E = {
|
|
|
81
81
|
return k.applicationId || (e.applicationId = H("devportal.createWebhook.errors.appRequired", "Select an application")), k.name.trim() || (e.name = H("devportal.createWebhook.errors.nameRequired", "Name is required")), k.url.trim() ? /^https:\/\//i.test(k.url) || (e.url = H("devportal.createWebhook.errors.urlHttps", "Endpoint URL must use HTTPS")) : e.url = H("devportal.createWebhook.errors.urlRequired", "Endpoint URL is required"), k.events.length === 0 && (e.events = H("devportal.createWebhook.errors.eventsRequired", "Select at least one event")), k.retryEnabled && (k.maxRetries < 1 || k.maxRetries > 10) && (e.maxRetries = H("devportal.createWebhook.errors.retriesRange", "Retry count must be between 1 and 10")), M(e), Object.keys(e).length === 0;
|
|
82
82
|
};
|
|
83
83
|
return /* @__PURE__ */ C(f, {
|
|
84
|
-
permission:
|
|
84
|
+
permission: h.WEBHOOK_CREATE,
|
|
85
85
|
children: /* @__PURE__ */ C(o, {
|
|
86
86
|
title: H("devportal.createWebhook.title", "Create Webhook"),
|
|
87
87
|
description: H("devportal.createWebhook.description", "Set up a real webhook endpoint to receive platform events through the public gateway"),
|
|
@@ -122,7 +122,7 @@ var E = {
|
|
|
122
122
|
variant: "error",
|
|
123
123
|
children: N
|
|
124
124
|
}),
|
|
125
|
-
R && !
|
|
125
|
+
R && !m && /* @__PURE__ */ C(d, {
|
|
126
126
|
variant: "error",
|
|
127
127
|
children: a(R)
|
|
128
128
|
}),
|
|
@@ -131,7 +131,7 @@ var E = {
|
|
|
131
131
|
children: /* @__PURE__ */ w("div", {
|
|
132
132
|
className: "space-y-4 rounded-card border border-border-seam bg-bg-surface p-4",
|
|
133
133
|
children: [
|
|
134
|
-
!
|
|
134
|
+
!m && /* @__PURE__ */ w("label", {
|
|
135
135
|
className: "grid gap-2 text-sm font-medium",
|
|
136
136
|
children: [
|
|
137
137
|
/* @__PURE__ */ C("span", { children: H("devportal.createWebhook.application", "Application") }),
|
|
@@ -155,7 +155,7 @@ var E = {
|
|
|
155
155
|
})
|
|
156
156
|
]
|
|
157
157
|
}),
|
|
158
|
-
L && !
|
|
158
|
+
L && !m && /* @__PURE__ */ w("div", {
|
|
159
159
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
160
160
|
role: "status",
|
|
161
161
|
"aria-live": "polite",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AAyB3C,eAAO,MAAM,aAAa,EAAE,EA6O3B,CAAC"}
|