@burdenoff/microfe-store 2026.1013.1 → 2026.1013.3
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/components/InstallAppModal.js +1 -1
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/pages/AdminFulfillmentLogsPage.js +1 -1
- package/dist/pages/AdminFulfillmentLogsPage.js.map +1 -1
- package/dist/pages/AdminOrdersPage.js +1 -1
- package/dist/pages/AdminOrdersPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js +1 -1
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/CreateSourcingRequestPage.js +1 -1
- package/dist/pages/CreateSourcingRequestPage.js.map +1 -1
- package/dist/pages/MarketplaceHomePage.js +1 -1
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/dist/pages/SellerSourcingOffersPage.js +1 -1
- package/dist/pages/SellerSourcingOffersPage.js.map +1 -1
- package/dist/pages/SellerSourcingOpportunityDetailPage.js +1 -1
- package/dist/pages/SellerSourcingOpportunityDetailPage.js.map +1 -1
- package/dist/pages/SourcingRequestsPage.js +1 -1
- package/dist/pages/SourcingRequestsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerSourcingOffersPage.js","names":[],"sources":["../../src/pages/SellerSourcingOffersPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport type { FC } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n ArrowRight,\n CalendarDays,\n CircleDollarSign,\n MapPin,\n PackageSearch,\n RefreshCw,\n TicketCheck,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingOfferStatus } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport { type SellerSourcingOffer, useSourcingApi } from '../hooks/useSourcing';\nimport { usePublisherSourcingAccess } from '../providers/SourcingAccessProvider';\nimport {\n ContextRequiredPanel,\n formatDate,\n formatDateTime,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n joinStorePath,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingEmptyState,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\nconst PAGE_SIZE = 30;\ntype OfferFilter = SourcingOfferStatus | '';\n\nfunction parseOfferFilter(value: string): OfferFilter {\n switch (value) {\n case SourcingOfferStatus.Active:\n return SourcingOfferStatus.Active;\n case SourcingOfferStatus.Accepted:\n return SourcingOfferStatus.Accepted;\n case SourcingOfferStatus.Rejected:\n return SourcingOfferStatus.Rejected;\n default:\n return '';\n }\n}\n\nexport const SellerSourcingOffersPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const {\n profile,\n loading: accessLoading,\n error: accessError,\n isEligible,\n refresh: refreshAccess,\n } = usePublisherSourcingAccess();\n const { listSellerOffers } = useSourcingApi();\n const [offers, setOffers] = useState<SellerSourcingOffer[]>([]);\n const [status, setStatus] = useState<OfferFilter>('');\n const [offset, setOffset] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(\n async (nextOffset = 0, append = false) => {\n if (!isEligible) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listSellerOffers({\n status: status || undefined,\n limit: PAGE_SIZE,\n offset: nextOffset,\n });\n setOffers((current) => (append ? [...current, ...result] : result));\n setOffset(nextOffset);\n setHasMore(result.length === PAGE_SIZE);\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'Your sourcing offers could not be loaded.'));\n } finally {\n setLoading(false);\n }\n },\n [isEligible, listSellerOffers, status]\n );\n\n useEffect(() => {\n void load();\n }, [load]);\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title=\"My sourcing offers\"\n description=\"Track and update the offers submitted by your verified Store Publisher.\"\n icon={<TicketCheck className=\"size-5\" aria-hidden=\"true\" />}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(joinStorePath(basePath, 'publisher/sourcing/opportunities'))}\n className={primaryButtonClassName}\n >\n <PackageSearch className=\"size-4\" aria-hidden=\"true\" />\n Find opportunities\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Active offers can be updated while their request remains open. Accepted and rejected\n offers are final. When a buyer accepts your offer, the resulting Store order and\n fulfillment status appear here without exposing another publisher's private offer.\n </PagePurpose>\n\n {accessLoading ? (\n <SourcingLoadingState label=\"Checking Store Publisher access…\" />\n ) : accessError ? (\n <SourcingErrorState\n title=\"Publisher access could not be checked\"\n message={accessError}\n onRetry={() => void refreshAccess()}\n />\n ) : !profile ? (\n <ContextRequiredPanel\n title=\"Store Publisher profile required\"\n description=\"A Store Publisher profile is required to submit or review sourcing offers.\"\n />\n ) : !isEligible ? (\n <ContextRequiredPanel\n title=\"Verified publisher status required\"\n description={`The publisher profile “${profile.name}” is currently ${profile.verificationStatus.toLowerCase().replaceAll('_', ' ')}. Offers are available only to active verified publishers.`}\n />\n ) : (\n <>\n <div className=\"mb-5 flex flex-col gap-3 rounded-xl border border-border-subtle bg-surface-elevated p-4 sm:flex-row sm:items-end sm:justify-between\">\n <label className=\"block sm:w-64\">\n <span className=\"text-sm font-medium text-text-primary\">Offer status</span>\n <select\n value={status}\n onChange={(event) => setStatus(parseOfferFilter(event.target.value))}\n className=\"mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20\"\n >\n <option value=\"\">All statuses</option>\n <option value={SourcingOfferStatus.Active}>Active</option>\n <option value={SourcingOfferStatus.Accepted}>Accepted</option>\n <option value={SourcingOfferStatus.Rejected}>Rejected</option>\n </select>\n </label>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n <RefreshCw\n className={`size-4 ${loading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n Refresh\n </button>\n </div>\n\n {loading && offers.length === 0 ? (\n <SourcingLoadingState label=\"Loading your sourcing offers…\" />\n ) : error && offers.length === 0 ? (\n <SourcingErrorState\n title=\"Offers could not be loaded\"\n message={error}\n onRetry={() => void load()}\n />\n ) : offers.length === 0 ? (\n <SourcingEmptyState\n title={status ? 'No offers match this status' : 'No sourcing offers yet'}\n description={\n status\n ? 'Choose another status to review the rest of your offers.'\n : 'Browse open buyer opportunities to create your first sourcing offer.'\n }\n action={\n <button\n type=\"button\"\n onClick={() =>\n navigate(joinStorePath(basePath, 'publisher/sourcing/opportunities'))\n }\n className={primaryButtonClassName}\n >\n <PackageSearch className=\"size-4\" aria-hidden=\"true\" />\n Browse opportunities\n </button>\n }\n />\n ) : (\n <>\n {error ? (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {error}\n </div>\n ) : null}\n <ul className=\"grid gap-4 md:grid-cols-2\">\n {offers.map((offer) => (\n <li key={offer.id}>\n <GlassCard className=\"flex h-full flex-col p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-semibold uppercase tracking-wide text-accent-primary\">\n {offer.request?.category ?? 'Sourcing request'}\n </p>\n <h2 className=\"mt-1 line-clamp-2 text-lg font-semibold text-text-primary\">\n {offer.request?.productName ?? `Request ${offer.requestId}`}\n </h2>\n </div>\n <SourcingStatusBadge status={offer.status} />\n </div>\n\n <dl className=\"mt-5 grid gap-4 text-sm sm:grid-cols-2\">\n <div className=\"flex gap-2\">\n <CircleDollarSign\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Your unit price</dt>\n <dd className=\"font-semibold text-text-primary\">\n {formatMoney(offer.unitPrice, offer.currency)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <PackageSearch\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Quantity</dt>\n <dd className=\"font-medium text-text-primary\">\n {offer.quantity.toLocaleString()} {formatSourcingUnit(offer.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <CalendarDays\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Promised delivery</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDateTime(offer.deliveryDate)}\n </dd>\n </div>\n </div>\n {offer.request ? (\n <div className=\"flex gap-2\">\n <MapPin\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Destination</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatLocation({\n country: offer.request.deliveryCountry,\n region: offer.request.deliveryRegion,\n city: offer.request.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n ) : null}\n </dl>\n\n {offer.message ? (\n <p className=\"mt-4 line-clamp-3 rounded-lg bg-surface-sunken p-3 text-sm leading-6 text-text-secondary\">\n {offer.message}\n </p>\n ) : null}\n\n {offer.order ? (\n <div className=\"mt-4 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3\">\n <p className=\"text-xs font-medium text-status-success-text\">\n Store order {offer.order.id}\n </p>\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">\n {formatMoney(offer.order.total, offer.order.currency)} ·{' '}\n {offer.order.status}\n </p>\n {offer.order.fulfillment ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n Fulfillment: {offer.order.fulfillment.status.replaceAll('_', ' ')}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"mt-auto flex items-center justify-between gap-3 border-t border-border-subtle pt-4\">\n <p className=\"text-xs text-text-muted\">\n Updated {formatDate(offer.updatedAt)}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n joinStorePath(\n basePath,\n `publisher/sourcing/opportunities/${offer.requestId}`\n )\n )\n }\n className={secondaryButtonClassName}\n >\n {offer.status === SourcingOfferStatus.Active\n ? 'Review or update'\n : 'View details'}\n <ArrowRight className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </GlassCard>\n </li>\n ))}\n </ul>\n\n {hasMore ? (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={() => void load(offset + PAGE_SIZE, true)}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </div>\n ) : null}\n </>\n )}\n </>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SellerSourcingOffersPage.js","names":[],"sources":["../../src/pages/SellerSourcingOffersPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport type { FC } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n ArrowRight,\n CalendarDays,\n CircleDollarSign,\n MapPin,\n PackageSearch,\n RefreshCw,\n TicketCheck,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingOfferStatus } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport { type SellerSourcingOffer, useSourcingApi } from '../hooks/useSourcing';\nimport { usePublisherSourcingAccess } from '../providers/SourcingAccessProvider';\nimport {\n ContextRequiredPanel,\n formatDate,\n formatDateTime,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n joinStorePath,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingEmptyState,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\nconst PAGE_SIZE = 30;\ntype OfferFilter = SourcingOfferStatus | '';\n\nfunction parseOfferFilter(value: string): OfferFilter {\n switch (value) {\n case SourcingOfferStatus.Active:\n return SourcingOfferStatus.Active;\n case SourcingOfferStatus.Accepted:\n return SourcingOfferStatus.Accepted;\n case SourcingOfferStatus.Rejected:\n return SourcingOfferStatus.Rejected;\n default:\n return '';\n }\n}\n\nexport const SellerSourcingOffersPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const {\n profile,\n loading: accessLoading,\n error: accessError,\n isEligible,\n refresh: refreshAccess,\n } = usePublisherSourcingAccess();\n const { listSellerOffers } = useSourcingApi();\n const [offers, setOffers] = useState<SellerSourcingOffer[]>([]);\n const [status, setStatus] = useState<OfferFilter>('');\n const [offset, setOffset] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(\n async (nextOffset = 0, append = false) => {\n if (!isEligible) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listSellerOffers({\n status: status || undefined,\n limit: PAGE_SIZE,\n offset: nextOffset,\n });\n setOffers((current) => (append ? [...current, ...result] : result));\n setOffset(nextOffset);\n setHasMore(result.length === PAGE_SIZE);\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'Your sourcing offers could not be loaded.'));\n } finally {\n setLoading(false);\n }\n },\n [isEligible, listSellerOffers, status]\n );\n\n useEffect(() => {\n void load();\n }, [load]);\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title=\"My sourcing offers\"\n description=\"Track and update the offers submitted by your verified Store Publisher.\"\n icon={<TicketCheck className=\"size-5\" aria-hidden=\"true\" />}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(joinStorePath(basePath, 'publisher/sourcing/opportunities'))}\n className={primaryButtonClassName}\n >\n <PackageSearch className=\"size-4\" aria-hidden=\"true\" />\n Find opportunities\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Active offers can be updated while their request remains open. Accepted and rejected\n offers are final. When a buyer accepts your offer, the resulting Store order and\n fulfillment status appear here without exposing another publisher's private offer.\n </PagePurpose>\n\n {accessLoading ? (\n <SourcingLoadingState label=\"Checking Store Publisher access…\" />\n ) : accessError ? (\n <SourcingErrorState\n title=\"Publisher access could not be checked\"\n message={accessError}\n onRetry={() => void refreshAccess()}\n />\n ) : !profile ? (\n <ContextRequiredPanel\n title=\"Store Publisher profile required\"\n description=\"A Store Publisher profile is required to submit or review sourcing offers.\"\n />\n ) : !isEligible ? (\n <ContextRequiredPanel\n title=\"Verified publisher status required\"\n description={`The publisher profile “${profile.name}” is currently ${profile.verificationStatus.toLowerCase().replaceAll('_', ' ')}. Offers are available only to active verified publishers.`}\n />\n ) : (\n <>\n <div className=\"mb-5 flex flex-col gap-3 rounded-xl border border-border-subtle bg-surface-elevated p-4 sm:flex-row sm:items-end sm:justify-between\">\n <label className=\"block sm:w-64\">\n <span className=\"text-sm font-medium text-text-primary\">Offer status</span>\n <select\n value={status}\n onChange={(event) => setStatus(parseOfferFilter(event.target.value))}\n className=\"mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All statuses</option>\n <option value={SourcingOfferStatus.Active}>Active</option>\n <option value={SourcingOfferStatus.Accepted}>Accepted</option>\n <option value={SourcingOfferStatus.Rejected}>Rejected</option>\n </select>\n </label>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n <RefreshCw\n className={`size-4 ${loading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n Refresh\n </button>\n </div>\n\n {loading && offers.length === 0 ? (\n <SourcingLoadingState label=\"Loading your sourcing offers…\" />\n ) : error && offers.length === 0 ? (\n <SourcingErrorState\n title=\"Offers could not be loaded\"\n message={error}\n onRetry={() => void load()}\n />\n ) : offers.length === 0 ? (\n <SourcingEmptyState\n title={status ? 'No offers match this status' : 'No sourcing offers yet'}\n description={\n status\n ? 'Choose another status to review the rest of your offers.'\n : 'Browse open buyer opportunities to create your first sourcing offer.'\n }\n action={\n <button\n type=\"button\"\n onClick={() =>\n navigate(joinStorePath(basePath, 'publisher/sourcing/opportunities'))\n }\n className={primaryButtonClassName}\n >\n <PackageSearch className=\"size-4\" aria-hidden=\"true\" />\n Browse opportunities\n </button>\n }\n />\n ) : (\n <>\n {error ? (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {error}\n </div>\n ) : null}\n <ul className=\"grid gap-4 md:grid-cols-2\">\n {offers.map((offer) => (\n <li key={offer.id}>\n <GlassCard className=\"flex h-full flex-col p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-semibold uppercase tracking-wide text-accent-primary\">\n {offer.request?.category ?? 'Sourcing request'}\n </p>\n <h2 className=\"mt-1 line-clamp-2 text-lg font-semibold text-text-primary\">\n {offer.request?.productName ?? `Request ${offer.requestId}`}\n </h2>\n </div>\n <SourcingStatusBadge status={offer.status} />\n </div>\n\n <dl className=\"mt-5 grid gap-4 text-sm sm:grid-cols-2\">\n <div className=\"flex gap-2\">\n <CircleDollarSign\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Your unit price</dt>\n <dd className=\"font-semibold text-text-primary\">\n {formatMoney(offer.unitPrice, offer.currency)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <PackageSearch\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Quantity</dt>\n <dd className=\"font-medium text-text-primary\">\n {offer.quantity.toLocaleString()} {formatSourcingUnit(offer.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <CalendarDays\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Promised delivery</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDateTime(offer.deliveryDate)}\n </dd>\n </div>\n </div>\n {offer.request ? (\n <div className=\"flex gap-2\">\n <MapPin\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Destination</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatLocation({\n country: offer.request.deliveryCountry,\n region: offer.request.deliveryRegion,\n city: offer.request.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n ) : null}\n </dl>\n\n {offer.message ? (\n <p className=\"mt-4 line-clamp-3 rounded-lg bg-surface-sunken p-3 text-sm leading-6 text-text-secondary\">\n {offer.message}\n </p>\n ) : null}\n\n {offer.order ? (\n <div className=\"mt-4 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3\">\n <p className=\"text-xs font-medium text-status-success-text\">\n Store order {offer.order.id}\n </p>\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">\n {formatMoney(offer.order.total, offer.order.currency)} ·{' '}\n {offer.order.status}\n </p>\n {offer.order.fulfillment ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n Fulfillment: {offer.order.fulfillment.status.replaceAll('_', ' ')}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"mt-auto flex items-center justify-between gap-3 border-t border-border-subtle pt-4\">\n <p className=\"text-xs text-text-muted\">\n Updated {formatDate(offer.updatedAt)}\n </p>\n <button\n type=\"button\"\n onClick={() =>\n navigate(\n joinStorePath(\n basePath,\n `publisher/sourcing/opportunities/${offer.requestId}`\n )\n )\n }\n className={secondaryButtonClassName}\n >\n {offer.status === SourcingOfferStatus.Active\n ? 'Review or update'\n : 'View details'}\n <ArrowRight className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </GlassCard>\n </li>\n ))}\n </ul>\n\n {hasMore ? (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={() => void load(offset + PAGE_SIZE, true)}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </div>\n ) : null}\n </>\n )}\n </>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -338,8 +338,8 @@ var C = () => {
|
|
|
338
338
|
value: R.storeProductId,
|
|
339
339
|
onChange: Q("storeProductId"),
|
|
340
340
|
disabled: D,
|
|
341
|
+
className: `${d} scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary`,
|
|
341
342
|
"aria-describedby": O ? "store-product-error" : "store-product-help",
|
|
342
|
-
className: d,
|
|
343
343
|
children: [/* @__PURE__ */ y("option", {
|
|
344
344
|
value: "",
|
|
345
345
|
children: D ? "Loading published products…" : "No linked Store product"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SellerSourcingOpportunityDetailPage.js","names":[],"sources":["../../src/pages/SellerSourcingOpportunityDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { ChangeEvent, FC, FormEvent } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport {\n BadgeCheck,\n CalendarDays,\n Check,\n CircleDollarSign,\n Loader2,\n MapPin,\n PackageSearch,\n Save,\n TicketCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingOfferStatus } from '../generated/global-types';\nimport type { UpsertSourcingOfferInput } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport {\n type SellerSourcingOfferDetail,\n type SellerSourcingOpportunityDetail,\n useSourcingApi,\n} from '../hooks/useSourcing';\nimport { usePublisherSourcingAccess } from '../providers/SourcingAccessProvider';\nimport {\n ContextRequiredPanel,\n formatDateTime,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n helpClassName,\n inputClassName,\n joinStorePath,\n labelClassName,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toDateTimeLocal,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\ninterface OfferFormState {\n unitPrice: string;\n deliveryDate: string;\n storeProductId: string;\n message: string;\n}\n\ntype OfferField = keyof OfferFormState;\ntype OfferErrors = Partial<Record<OfferField, string>>;\n\nconst EMPTY_FORM: OfferFormState = {\n unitPrice: '',\n deliveryDate: '',\n storeProductId: '',\n message: '',\n};\nconst MONEY_PATTERN = /^(?:0|[1-9]\\d{0,15})(?:\\.\\d{1,2})?$/;\n\nfunction validateOffer(\n form: OfferFormState,\n opportunity: SellerSourcingOpportunityDetail\n): OfferErrors {\n const errors: OfferErrors = {};\n const amount = form.unitPrice.trim();\n const delivery = new Date(form.deliveryDate);\n const start = new Date(opportunity.deliveryWindowStart);\n const end = new Date(opportunity.deliveryWindowEnd);\n\n if (!MONEY_PATTERN.test(amount) || Number(amount) <= 0) {\n errors.unitPrice = 'Enter a positive unit price with no more than two decimal places.';\n }\n if (\n !form.deliveryDate ||\n Number.isNaN(delivery.getTime()) ||\n delivery < start ||\n delivery > end\n ) {\n errors.deliveryDate = 'Choose a delivery date within the buyer’s requested window.';\n }\n if (form.message.trim().length > 1000) {\n errors.message = 'Offer message cannot exceed 1,000 characters.';\n }\n return errors;\n}\n\nexport const SellerSourcingOpportunityDetailPage: FC = () => {\n const navigate = useNavigate();\n const { requestId } = useParams<{ requestId: string }>();\n const { basePath } = useStore();\n const {\n profile,\n products,\n productsLoading,\n productsError,\n loading: accessLoading,\n error: accessError,\n isEligible,\n refresh: refreshAccess,\n refreshProducts,\n } = usePublisherSourcingAccess();\n const { getOpportunity, upsertOffer } = useSourcingApi();\n const [opportunity, setOpportunity] = useState<SellerSourcingOpportunityDetail | null>(null);\n const [offer, setOffer] = useState<SellerSourcingOfferDetail | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [form, setForm] = useState<OfferFormState>(EMPTY_FORM);\n const [errors, setErrors] = useState<OfferErrors>({});\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n const [submitting, setSubmitting] = useState(false);\n\n const opportunitiesPath = joinStorePath(basePath, 'publisher/sourcing/opportunities');\n\n const load = useCallback(\n async (initializeForm = false) => {\n if (!isEligible || !requestId) return;\n setLoading(true);\n setError(null);\n try {\n const result = await getOpportunity(requestId);\n setOpportunity(result.opportunity);\n setOffer(result.offer);\n setIsOpen(result.isOpen);\n if (!result.opportunity) {\n setError('This sourcing opportunity is no longer available.');\n } else if (initializeForm) {\n setForm({\n unitPrice: result.offer?.unitPrice ?? '',\n deliveryDate: result.offer\n ? toDateTimeLocal(result.offer.deliveryDate)\n : toDateTimeLocal(result.opportunity.deliveryWindowStart),\n storeProductId: result.offer?.storeProductId ?? '',\n message: result.offer?.message ?? '',\n });\n }\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'The sourcing opportunity could not be loaded.'));\n } finally {\n setLoading(false);\n }\n },\n [getOpportunity, isEligible, requestId]\n );\n\n useEffect(() => {\n void load(true);\n }, [load]);\n\n const publishedProducts = useMemo(\n () =>\n [...products]\n .filter((product) => product.status === 'PUBLISHED')\n .sort((left, right) => left.name.localeCompare(right.name)),\n [products]\n );\n\n const updateField =\n (field: OfferField) =>\n (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {\n setForm((current) => ({ ...current, [field]: event.target.value }));\n setErrors((current) => ({ ...current, [field]: undefined }));\n setSuccess(null);\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!opportunity || !requestId || !isOpen || submitting) return;\n\n const validationErrors = validateOffer(form, opportunity);\n setErrors(validationErrors);\n setSubmitError(null);\n setSuccess(null);\n if (Object.keys(validationErrors).length > 0) return;\n\n const input: UpsertSourcingOfferInput = {\n requestId,\n quantity: opportunity.quantity,\n unit: opportunity.unit,\n unitPrice: form.unitPrice.trim(),\n currency: 'USD',\n deliveryDate: new Date(form.deliveryDate).toISOString(),\n storeProductId: form.storeProductId || undefined,\n message: form.message.trim() || undefined,\n };\n\n setSubmitting(true);\n try {\n const saved = await upsertOffer(input);\n setSuccess(offer ? 'Your offer was updated.' : 'Your offer was submitted.');\n setOffer(saved);\n await load(false);\n } catch (saveError) {\n setSubmitError(\n toErrorMessage(\n saveError,\n 'Your offer could not be saved. The request may have closed while you were editing.'\n )\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n const fieldError = (field: OfferField) => {\n const message = errors[field];\n return message ? (\n <p id={`${field}-error`} className=\"mt-1 text-xs text-status-error-text\">\n {message}\n </p>\n ) : null;\n };\n\n const canEdit = isOpen && (!offer || offer.status === SourcingOfferStatus.Active);\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title={opportunity?.productName ?? 'Sourcing opportunity'}\n description=\"Review the sanitized buyer request and create or update your publisher’s offer.\"\n icon={<TicketCheck className=\"size-5\" aria-hidden=\"true\" />}\n onBack={() => navigate(opportunitiesPath)}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(joinStorePath(basePath, 'publisher/sourcing/offers'))}\n className={secondaryButtonClassName}\n >\n My offers\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Quantity and unit are fixed by the buyer request. Store validates publisher identity, USD\n settlement price, delivery window, and optional linked product on every save. You can\n update one active offer until the request closes.\n </PagePurpose>\n\n {accessLoading ? (\n <SourcingLoadingState label=\"Checking Store Publisher access…\" />\n ) : accessError ? (\n <SourcingErrorState\n title=\"Publisher access could not be checked\"\n message={accessError}\n onRetry={() => void refreshAccess()}\n />\n ) : !profile ? (\n <ContextRequiredPanel\n title=\"Store Publisher profile required\"\n description=\"Only users who own a verified Store Publisher profile can view this opportunity or submit an offer.\"\n />\n ) : !isEligible ? (\n <ContextRequiredPanel\n title=\"Verified publisher status required\"\n description={`The publisher profile “${profile.name}” is currently ${profile.verificationStatus.toLowerCase().replaceAll('_', ' ')}.`}\n />\n ) : loading && !opportunity ? (\n <SourcingLoadingState label=\"Loading opportunity and your offer…\" />\n ) : error && !opportunity ? (\n <SourcingErrorState\n title=\"Opportunity unavailable\"\n message={error}\n onRetry={requestId ? () => void load(true) : undefined}\n />\n ) : opportunity ? (\n <>\n {submitError ? (\n <div\n role=\"alert\"\n className=\"mb-5 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n ) : null}\n {success ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"mb-5 flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\"\n >\n <Check className=\"size-4\" aria-hidden=\"true\" />\n {success}\n </div>\n ) : null}\n\n {offer?.order ? (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-sm font-semibold text-status-success-text\">\n This offer was accepted\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Store order {offer.order.id} ·{' '}\n {formatMoney(offer.order.total, offer.order.currency)} · {offer.order.status}\n </p>\n </div>\n {offer.order.fulfillment ? (\n <span className=\"rounded-full border border-border-default bg-surface-sunken px-3 py-1.5 text-xs font-medium text-text-secondary\">\n Fulfillment {offer.order.fulfillment.status.replaceAll('_', ' ')}\n </span>\n ) : null}\n </div>\n </EmphasisPanel>\n ) : null}\n\n <div className=\"grid gap-6 lg:grid-cols-[minmax(0,1.2fr)_minmax(340px,0.8fr)]\">\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <p className=\"text-sm font-medium text-accent-primary\">\n {opportunity.category}\n </p>\n <h2 className=\"mt-1 text-xl font-semibold text-text-primary\">\n {opportunity.productName}\n </h2>\n </div>\n <span\n className={`rounded-full border px-2.5 py-1 text-xs font-semibold ${\n isOpen\n ? 'border-status-success-border bg-status-success-bg-subtle text-status-success-text'\n : 'border-border-default bg-surface-sunken text-text-secondary'\n }`}\n >\n {isOpen ? 'OPEN' : 'CLOSED'}\n </span>\n </div>\n\n {opportunity.summary ? (\n <p className=\"mt-4 whitespace-pre-wrap text-sm leading-6 text-text-secondary\">\n {opportunity.summary}\n </p>\n ) : null}\n\n <dl className=\"mt-6 grid gap-5 border-t border-border-subtle pt-5 sm:grid-cols-2\">\n <div className=\"flex gap-3\">\n <PackageSearch\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Required quantity</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {opportunity.quantity.toLocaleString()}{' '}\n {formatSourcingUnit(opportunity.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <CircleDollarSign\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Buyer target</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatMoney(opportunity.targetUnitPrice, opportunity.targetCurrency)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <CalendarDays\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Delivery window</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDateTime(opportunity.deliveryWindowStart)} –{' '}\n {formatDateTime(opportunity.deliveryWindowEnd)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <MapPin className=\"mt-0.5 size-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n <div>\n <dt className=\"text-xs text-text-muted\">Destination</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatLocation({\n country: opportunity.deliveryCountry,\n region: opportunity.deliveryRegion,\n city: opportunity.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n </dl>\n </GlassCard>\n\n {canEdit ? (\n <form onSubmit={(event) => void handleSubmit(event)} noValidate>\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {offer ? 'Update your offer' : 'Create an offer'}\n </h2>\n <p className=\"mt-1 text-xs text-text-muted\">Offering as {profile.name}</p>\n </div>\n <BadgeCheck\n className=\"size-5 text-accent-primary\"\n aria-label=\"Verified publisher\"\n />\n </div>\n\n <div className=\"mt-5 space-y-5\">\n <label className={labelClassName}>\n Unit price\n <div className=\"mt-1.5 grid grid-cols-[1fr_96px] gap-2\">\n <input\n type=\"text\"\n value={form.unitPrice}\n onChange={updateField('unitPrice')}\n inputMode=\"decimal\"\n required\n maxLength={19}\n aria-invalid={Boolean(errors.unitPrice)}\n aria-describedby={\n errors.unitPrice ? 'unitPrice-error' : 'unitPrice-help'\n }\n placeholder=\"125.50\"\n className={`${inputClassName} mt-0`}\n />\n <span\n aria-label=\"Offer currency\"\n className=\"mt-0 flex items-center justify-center rounded-md border border-border-default bg-surface-sunken px-3 text-sm font-semibold text-text-secondary\"\n >\n USD\n </span>\n </div>\n {fieldError('unitPrice')}\n {!errors.unitPrice ? (\n <p id=\"unitPrice-help\" className={helpClassName}>\n Price per {formatSourcingUnit(opportunity.unit).toLowerCase()}. Store\n sourcing currently settles in USD.\n </p>\n ) : null}\n </label>\n\n <label className={labelClassName}>\n Promised delivery date\n <input\n type=\"datetime-local\"\n value={form.deliveryDate}\n onChange={updateField('deliveryDate')}\n required\n min={toDateTimeLocal(opportunity.deliveryWindowStart)}\n max={toDateTimeLocal(opportunity.deliveryWindowEnd)}\n aria-invalid={Boolean(errors.deliveryDate)}\n aria-describedby={\n errors.deliveryDate ? 'deliveryDate-error' : 'deliveryDate-help'\n }\n className={inputClassName}\n />\n {fieldError('deliveryDate')}\n {!errors.deliveryDate ? (\n <p id=\"deliveryDate-help\" className={helpClassName}>\n Must fall inside the buyer’s delivery window.\n </p>\n ) : null}\n </label>\n\n <label className={labelClassName}>\n Link a published product{' '}\n <span className=\"font-normal text-text-muted\">(optional)</span>\n <select\n value={form.storeProductId}\n onChange={updateField('storeProductId')}\n disabled={productsLoading}\n aria-describedby={\n productsError ? 'store-product-error' : 'store-product-help'\n }\n className={inputClassName}\n >\n <option value=\"\">\n {productsLoading\n ? 'Loading published products…'\n : 'No linked Store product'}\n </option>\n {publishedProducts.map((product) => (\n <option key={product.id} value={product.id}>\n {product.name}\n </option>\n ))}\n </select>\n {productsError ? (\n <div\n id=\"store-product-error\"\n role=\"alert\"\n className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-status-error-text\"\n >\n <span>{productsError}</span>\n <button\n type=\"button\"\n onClick={() => void refreshProducts()}\n className=\"font-semibold underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg\"\n >\n Try again\n </button>\n </div>\n ) : (\n <p id=\"store-product-help\" className={helpClassName}>\n Only products owned by this publisher and currently published are\n accepted. The link is reference metadata and does not reserve catalog\n inventory or create digital entitlements.\n </p>\n )}\n </label>\n\n <label className={labelClassName}>\n Message <span className=\"font-normal text-text-muted\">(optional)</span>\n <textarea\n value={form.message}\n onChange={updateField('message')}\n maxLength={1000}\n rows={4}\n aria-invalid={Boolean(errors.message)}\n aria-describedby={errors.message ? 'message-error' : 'message-help'}\n placeholder=\"Add packaging, quality, or delivery notes relevant to this offer.\"\n className={`${inputClassName} resize-y`}\n />\n {fieldError('message')}\n {!errors.message ? (\n <p id=\"message-help\" className={helpClassName}>\n {form.message.length}/1,000 characters\n </p>\n ) : null}\n </label>\n </div>\n\n <button\n type=\"submit\"\n disabled={submitting}\n className={`${primaryButtonClassName} mt-6 w-full`}\n >\n {submitting ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : offer ? (\n <Save className=\"size-4\" aria-hidden=\"true\" />\n ) : (\n <TicketCheck className=\"size-4\" aria-hidden=\"true\" />\n )}\n {submitting ? 'Saving offer…' : offer ? 'Update offer' : 'Submit offer'}\n </button>\n </GlassCard>\n </form>\n ) : offer ? (\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Your offer</h2>\n <p className=\"mt-1 text-xs text-text-muted\">\n Updated {formatDateTime(offer.updatedAt)}\n </p>\n </div>\n <SourcingStatusBadge status={offer.status} />\n </div>\n <dl className=\"mt-5 space-y-4 text-sm\">\n <div>\n <dt className=\"text-xs text-text-muted\">Unit price</dt>\n <dd className=\"mt-0.5 text-lg font-semibold text-text-primary\">\n {formatMoney(offer.unitPrice, offer.currency)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-muted\">Promised delivery</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDateTime(offer.deliveryDate)}\n </dd>\n </div>\n {offer.message ? (\n <div>\n <dt className=\"text-xs text-text-muted\">Message</dt>\n <dd className=\"mt-1 whitespace-pre-wrap text-text-secondary\">\n {offer.message}\n </dd>\n </div>\n ) : null}\n </dl>\n </GlassCard>\n ) : (\n <ContextRequiredPanel\n title=\"This request is closed\"\n description=\"The buyer matched or canceled this request before your publisher submitted an offer.\"\n />\n )}\n </div>\n </>\n ) : null}\n </main>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;AAsDA,IAAM,KAA6B;CACjC,WAAW;CACX,cAAc;CACd,gBAAgB;CAChB,SAAS;CACV,EACK,IAAgB;AAEtB,SAAS,GACP,GACA,GACa;CACb,IAAM,IAAsB,EAAE,EACxB,IAAS,EAAK,UAAU,MAAM,EAC9B,IAAW,IAAI,KAAK,EAAK,aAAa,EACtC,IAAQ,IAAI,KAAK,EAAY,oBAAoB,EACjD,IAAM,IAAI,KAAK,EAAY,kBAAkB;AAgBnD,SAdI,CAAC,EAAc,KAAK,EAAO,IAAI,OAAO,EAAO,IAAI,OACnD,EAAO,YAAY,uEAGnB,CAAC,EAAK,gBACN,OAAO,MAAM,EAAS,SAAS,CAAC,IAChC,IAAW,KACX,IAAW,OAEX,EAAO,eAAe,gEAEpB,EAAK,QAAQ,MAAM,CAAC,SAAS,QAC/B,EAAO,UAAU,kDAEZ;;AAGT,IAAa,UAAgD;CAC3D,IAAM,IAAW,IAAa,EACxB,EAAE,iBAAc,IAAkC,EAClD,EAAE,gBAAa,GAAU,EACzB,EACJ,YACA,aACA,oBACA,kBACA,SAAS,GACT,OAAO,GACP,eACA,SAAS,GACT,wBACE,GAA4B,EAC1B,EAAE,mBAAgB,oBAAgB,GAAgB,EAClD,CAAC,GAAa,MAAkB,EAAiD,KAAK,EACtF,CAAC,GAAO,KAAY,EAA2C,KAAK,EACpE,CAAC,GAAQ,MAAa,EAAS,GAAM,EACrC,CAAC,GAAM,KAAW,EAAyB,GAAW,EACtD,CAAC,GAAQ,KAAa,EAAsB,EAAE,CAAC,EAC/C,CAAC,IAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAS,KAAc,EAAwB,KAAK,EACrD,CAAC,GAAY,KAAiB,EAAS,GAAM,EAE7C,KAAoB,EAAc,GAAU,mCAAmC,EAE/E,IAAO,GACX,OAAO,IAAiB,OAAU;AAC5B,SAAC,KAAc,CAAC,IAEpB;GADA,EAAW,GAAK,EAChB,EAAS,KAAK;AACd,OAAI;IACF,IAAM,IAAS,MAAM,EAAe,EAAU;AAI9C,IAHA,GAAe,EAAO,YAAY,EAClC,EAAS,EAAO,MAAM,EACtB,GAAU,EAAO,OAAO,EACnB,EAAO,cAED,KACT,EAAQ;KACN,WAAW,EAAO,OAAO,aAAa;KACtC,cAAc,EAAO,QACjB,EAAgB,EAAO,MAAM,aAAa,GAC1C,EAAgB,EAAO,YAAY,oBAAoB;KAC3D,gBAAgB,EAAO,OAAO,kBAAkB;KAChD,SAAS,EAAO,OAAO,WAAW;KACnC,CAAC,GATF,EAAS,oDAAoD;YAWxD,GAAW;AAClB,MAAS,EAAe,GAAW,gDAAgD,CAAC;aAC5E;AACR,MAAW,GAAM;;;IAGrB;EAAC;EAAgB;EAAY;EAAU,CACxC;AAED,UAAgB;AACT,IAAK,GAAK;IACd,CAAC,EAAK,CAAC;CAEV,IAAM,KAAoB,SAEtB,CAAC,GAAG,EAAS,CACV,QAAQ,MAAY,EAAQ,WAAW,YAAY,CACnD,MAAM,GAAM,MAAU,EAAK,KAAK,cAAc,EAAM,KAAK,CAAC,EAC/D,CAAC,EAAS,CACX,EAEK,KACH,OACA,MAAmF;AAGlF,EAFA,GAAS,OAAa;GAAE,GAAG;IAAU,IAAQ,EAAM,OAAO;GAAO,EAAE,EACnE,GAAW,OAAa;GAAE,GAAG;IAAU,IAAQ,KAAA;GAAW,EAAE,EAC5D,EAAW,KAAK;IAGd,KAAe,OAAO,MAAsC;AAEhE,MADA,EAAM,gBAAgB,EAClB,CAAC,KAAe,CAAC,KAAa,CAAC,KAAU,EAAY;EAEzD,IAAM,IAAmB,GAAc,GAAM,EAAY;AAIzD,MAHA,EAAU,EAAiB,EAC3B,EAAe,KAAK,EACpB,EAAW,KAAK,EACZ,OAAO,KAAK,EAAiB,CAAC,SAAS,EAAG;EAE9C,IAAM,IAAkC;GACtC;GACA,UAAU,EAAY;GACtB,MAAM,EAAY;GAClB,WAAW,EAAK,UAAU,MAAM;GAChC,UAAU;GACV,cAAc,IAAI,KAAK,EAAK,aAAa,CAAC,aAAa;GACvD,gBAAgB,EAAK,kBAAkB,KAAA;GACvC,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;GACjC;AAED,IAAc,GAAK;AACnB,MAAI;GACF,IAAM,IAAQ,MAAM,GAAY,EAAM;AAGtC,GAFA,EAAW,IAAQ,4BAA4B,4BAA4B,EAC3E,EAAS,EAAM,EACf,MAAM,EAAK,GAAM;WACV,GAAW;AAClB,KACE,EACE,GACA,qFACD,CACF;YACO;AACR,KAAc,GAAM;;IAIlB,KAAc,MAAsB;EACxC,IAAM,IAAU,EAAO;AACvB,SAAO,IACL,kBAAC,KAAD;GAAG,IAAI,GAAG,EAAM;GAAS,WAAU;aAChC;GACC,CAAA,GACF;IAGA,KAAU,MAAW,CAAC,KAAS,EAAM,WAAW,EAAoB;AAE1E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,IAAD;GACE,OAAO,GAAa,eAAe;GACnC,aAAY;GACZ,MAAM,kBAAC,GAAD;IAAa,WAAU;IAAS,eAAY;IAAS,CAAA;GAC3D,cAAc,EAAS,GAAkB;GACzC,SACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAS,EAAc,GAAU,4BAA4B,CAAC;IAC7E,WAAW;cACZ;IAEQ,CAAA;GAEX,CAAA,EAEF,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,IAAD;IAAa,WAAU;cAAO;IAIhB,CAAA,EAEb,IACC,kBAAC,GAAD,EAAsB,OAAM,oCAAqC,CAAA,GAC/D,IACF,kBAAC,GAAD;IACE,OAAM;IACN,SAAS;IACT,eAAe,KAAK,GAAe;IACnC,CAAA,GACC,IAKA,IAKD,MAAW,CAAC,IACd,kBAAC,GAAD,EAAsB,OAAM,uCAAwC,CAAA,GAClE,KAAS,CAAC,IACZ,kBAAC,GAAD;IACE,OAAM;IACN,SAAS;IACT,SAAS,UAAkB,KAAK,EAAK,GAAK,GAAG,KAAA;IAC7C,CAAA,GACA,IACF,kBAAA,GAAA,EAAA,UAAA;IACG,IACC,kBAAC,OAAD;KACE,MAAK;KACL,WAAU;eAET;KACG,CAAA,GACJ;IACH,IACC,kBAAC,OAAD;KACE,MAAK;KACL,aAAU;KACV,WAAU;eAHZ,CAKE,kBAAC,IAAD;MAAO,WAAU;MAAS,eAAY;MAAS,CAAA,EAC9C,EACG;SACJ;IAEH,GAAO,QACN,kBAAC,IAAD;KAAe,WAAU;eACvB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAE1D,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAgD;QACjC,EAAM,MAAM;QAAG;QAAG;QAC9B,EAAY,EAAM,MAAM,OAAO,EAAM,MAAM,SAAS;QAAC;QAAI,EAAM,MAAM;QACpE;SACA,EAAA,CAAA,EACL,EAAM,MAAM,cACX,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAkI,gBACnH,EAAM,MAAM,YAAY,OAAO,WAAW,KAAK,IAAI,CAC3D;WACL,KACA;;KACQ,CAAA,GACd;IAEJ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,WAAU;gBAArB;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAY;SACX,CAAA,EACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAY;SACV,CAAA,CACD,EAAA,CAAA,EACN,kBAAC,QAAD;SACE,WAAW,yDACT,IACI,sFACA;mBAGL,IAAS,SAAS;SACd,CAAA,CACH;;OAEL,EAAY,UACX,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAY;QACX,CAAA,GACF;OAEJ,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,IAAD;WACE,WAAU;WACV,eAAY;WACZ,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;WAAI,WAAU;qBAA0B;WAAsB,CAAA,EAC9D,kBAAC,MAAD;WAAI,WAAU;qBAAd;YACG,EAAY,SAAS,gBAAgB;YAAE;YACvC,EAAmB,EAAY,KAAK;YAClC;aACD,EAAA,CAAA,CACF;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,IAAD;WACE,WAAU;WACV,eAAY;WACZ,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;WAAI,WAAU;qBAA0B;WAAiB,CAAA,EACzD,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAY,EAAY,iBAAiB,EAAY,eAAe;WAClE,CAAA,CACD,EAAA,CAAA,CACF;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,IAAD;WACE,WAAU;WACV,eAAY;WACZ,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;WAAI,WAAU;qBAA0B;WAAoB,CAAA,EAC5D,kBAAC,MAAD;WAAI,WAAU;qBAAd;YACG,EAAe,EAAY,oBAAoB;YAAC;YAAG;YACnD,EAAe,EAAY,kBAAkB;YAC3C;aACD,EAAA,CAAA,CACF;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,IAAD;WAAQ,WAAU;WAAyC,eAAY;WAAS,CAAA,EAChF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;WAAI,WAAU;qBAA0B;WAAgB,CAAA,EACxD,kBAAC,MAAD;WAAI,WAAU;qBACX,GAAe;YACd,SAAS,EAAY;YACrB,QAAQ,EAAY;YACpB,MAAM,EAAY;YACnB,CAAC;WACC,CAAA,CACD,EAAA,CAAA,CACF;;SACH;;OACK;SAEX,KACC,kBAAC,QAAD;MAAM,WAAW,MAAU,KAAK,GAAa,EAAM;MAAE,YAAA;gBACnD,kBAAC,GAAD;OAAW,WAAU;iBAArB;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,IAAQ,sBAAsB;UAC5B,CAAA,EACL,kBAAC,KAAD;UAAG,WAAU;oBAAb,CAA4C,gBAAa,EAAQ,KAAS;YACtE,EAAA,CAAA,EACN,kBAAC,IAAD;UACE,WAAU;UACV,cAAW;UACX,CAAA,CACE;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,SAAD;WAAO,WAAW;qBAAlB;YAAkC;YAEhC,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,SAAD;cACE,MAAK;cACL,OAAO,EAAK;cACZ,UAAU,EAAY,YAAY;cAClC,WAAU;cACV,UAAA;cACA,WAAW;cACX,gBAAc,EAAQ,EAAO;cAC7B,oBACE,EAAO,YAAY,oBAAoB;cAEzC,aAAY;cACZ,WAAW,GAAG,EAAe;cAC7B,CAAA,EACF,kBAAC,QAAD;cACE,cAAW;cACX,WAAU;wBACX;cAEM,CAAA,CACH;;YACL,EAAW,YAAY;YACtB,EAAO,YAKL,OAJF,kBAAC,KAAD;aAAG,IAAG;aAAiB,WAAW;uBAAlC;cAAiD;cACpC,EAAmB,EAAY,KAAK,CAAC,aAAa;cAAC;cAE5D;;YAEA;;UAER,kBAAC,SAAD;WAAO,WAAW;qBAAlB;YAAkC;YAEhC,kBAAC,SAAD;aACE,MAAK;aACL,OAAO,EAAK;aACZ,UAAU,EAAY,eAAe;aACrC,UAAA;aACA,KAAK,EAAgB,EAAY,oBAAoB;aACrD,KAAK,EAAgB,EAAY,kBAAkB;aACnD,gBAAc,EAAQ,EAAO;aAC7B,oBACE,EAAO,eAAe,uBAAuB;aAE/C,WAAW;aACX,CAAA;YACD,EAAW,eAAe;YACzB,EAAO,eAIL,OAHF,kBAAC,KAAD;aAAG,IAAG;aAAoB,WAAW;uBAAe;aAEhD,CAAA;YAEA;;UAER,kBAAC,SAAD;WAAO,WAAW;qBAAlB;YAAkC;YACP;YACzB,kBAAC,QAAD;aAAM,WAAU;uBAA8B;aAAiB,CAAA;YAC/D,kBAAC,UAAD;aACE,OAAO,EAAK;aACZ,UAAU,EAAY,iBAAiB;aACvC,UAAU;aACV,oBACE,IAAgB,wBAAwB;aAE1C,WAAW;uBAPb,CASE,kBAAC,UAAD;cAAQ,OAAM;wBACX,IACG,gCACA;cACG,CAAA,EACR,GAAkB,KAAK,MACtB,kBAAC,UAAD;cAAyB,OAAO,EAAQ;wBACrC,EAAQ;cACF,EAFI,EAAQ,GAEZ,CACT,CACK;;YACR,IACC,kBAAC,OAAD;aACE,IAAG;aACH,MAAK;aACL,WAAU;uBAHZ,CAKE,kBAAC,QAAD,EAAA,UAAO,GAAqB,CAAA,EAC5B,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,KAAK,IAAiB;cACrC,WAAU;wBACX;cAEQ,CAAA,CACL;iBAEN,kBAAC,KAAD;aAAG,IAAG;aAAqB,WAAW;uBAAe;aAIjD,CAAA;YAEA;;UAER,kBAAC,SAAD;WAAO,WAAW;qBAAlB;YAAkC;YACxB,kBAAC,QAAD;aAAM,WAAU;uBAA8B;aAAiB,CAAA;YACvE,kBAAC,YAAD;aACE,OAAO,EAAK;aACZ,UAAU,EAAY,UAAU;aAChC,WAAW;aACX,MAAM;aACN,gBAAc,EAAQ,EAAO;aAC7B,oBAAkB,EAAO,UAAU,kBAAkB;aACrD,aAAY;aACZ,WAAW,GAAG,EAAe;aAC7B,CAAA;YACD,EAAW,UAAU;YACpB,EAAO,UAIL,OAHF,kBAAC,KAAD;aAAG,IAAG;aAAe,WAAW;uBAAhC,CACG,EAAK,QAAQ,QAAO,oBACnB;;YAEA;;UACJ;;QAEN,kBAAC,UAAD;SACE,MAAK;SACL,UAAU;SACV,WAAW,GAAG,GAAuB;mBAHvC,CAKG,IACC,kBAAC,IAAD;UAAS,WAAU;UAAsB,eAAY;UAAS,CAAA,GAE9D,EADE,IACD,KAEA,GAFD;UAAM,WAAU;UAAS,eAAY;UAAS,CAEO,EAEtD,IAAa,kBAAkB,IAAQ,iBAAiB,eAClD;;QACC;;MACP,CAAA,GACL,IACF,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAe,CAAA,EACvE,kBAAC,KAAD;QAAG,WAAU;kBAAb,CAA4C,YACjC,EAAe,EAAM,UAAU,CACtC;UACA,EAAA,CAAA,EACN,kBAAC,IAAD,EAAqB,QAAQ,EAAM,QAAU,CAAA,CACzC;UACN,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA0B;SAAe,CAAA,EACvD,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAY,EAAM,WAAW,EAAM,SAAS;SAC1C,CAAA,CACD,EAAA,CAAA;QACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA0B;SAAsB,CAAA,EAC9D,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAe,EAAM,aAAa;SAChC,CAAA,CACD,EAAA,CAAA;QACL,EAAM,UACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA0B;SAAY,CAAA,EACpD,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAM;SACJ,CAAA,CACD,EAAA,CAAA,GACJ;QACD;SACK;UAEZ,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;MACZ,CAAA,CAEA;;IACL,EAAA,CAAA,GACD,OAhVF,kBAAC,GAAD;IACE,OAAM;IACN,aAAa,0BAA0B,EAAQ,KAAK,iBAAiB,EAAQ,mBAAmB,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC;IACnI,CAAA,GARF,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,CAAA,CAmVC;KACH"}
|
|
1
|
+
{"version":3,"file":"SellerSourcingOpportunityDetailPage.js","names":[],"sources":["../../src/pages/SellerSourcingOpportunityDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { ChangeEvent, FC, FormEvent } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport {\n BadgeCheck,\n CalendarDays,\n Check,\n CircleDollarSign,\n Loader2,\n MapPin,\n PackageSearch,\n Save,\n TicketCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingOfferStatus } from '../generated/global-types';\nimport type { UpsertSourcingOfferInput } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport {\n type SellerSourcingOfferDetail,\n type SellerSourcingOpportunityDetail,\n useSourcingApi,\n} from '../hooks/useSourcing';\nimport { usePublisherSourcingAccess } from '../providers/SourcingAccessProvider';\nimport {\n ContextRequiredPanel,\n formatDateTime,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n helpClassName,\n inputClassName,\n joinStorePath,\n labelClassName,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toDateTimeLocal,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\ninterface OfferFormState {\n unitPrice: string;\n deliveryDate: string;\n storeProductId: string;\n message: string;\n}\n\ntype OfferField = keyof OfferFormState;\ntype OfferErrors = Partial<Record<OfferField, string>>;\n\nconst EMPTY_FORM: OfferFormState = {\n unitPrice: '',\n deliveryDate: '',\n storeProductId: '',\n message: '',\n};\nconst MONEY_PATTERN = /^(?:0|[1-9]\\d{0,15})(?:\\.\\d{1,2})?$/;\n\nfunction validateOffer(\n form: OfferFormState,\n opportunity: SellerSourcingOpportunityDetail\n): OfferErrors {\n const errors: OfferErrors = {};\n const amount = form.unitPrice.trim();\n const delivery = new Date(form.deliveryDate);\n const start = new Date(opportunity.deliveryWindowStart);\n const end = new Date(opportunity.deliveryWindowEnd);\n\n if (!MONEY_PATTERN.test(amount) || Number(amount) <= 0) {\n errors.unitPrice = 'Enter a positive unit price with no more than two decimal places.';\n }\n if (\n !form.deliveryDate ||\n Number.isNaN(delivery.getTime()) ||\n delivery < start ||\n delivery > end\n ) {\n errors.deliveryDate = 'Choose a delivery date within the buyer’s requested window.';\n }\n if (form.message.trim().length > 1000) {\n errors.message = 'Offer message cannot exceed 1,000 characters.';\n }\n return errors;\n}\n\nexport const SellerSourcingOpportunityDetailPage: FC = () => {\n const navigate = useNavigate();\n const { requestId } = useParams<{ requestId: string }>();\n const { basePath } = useStore();\n const {\n profile,\n products,\n productsLoading,\n productsError,\n loading: accessLoading,\n error: accessError,\n isEligible,\n refresh: refreshAccess,\n refreshProducts,\n } = usePublisherSourcingAccess();\n const { getOpportunity, upsertOffer } = useSourcingApi();\n const [opportunity, setOpportunity] = useState<SellerSourcingOpportunityDetail | null>(null);\n const [offer, setOffer] = useState<SellerSourcingOfferDetail | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [form, setForm] = useState<OfferFormState>(EMPTY_FORM);\n const [errors, setErrors] = useState<OfferErrors>({});\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n const [submitting, setSubmitting] = useState(false);\n\n const opportunitiesPath = joinStorePath(basePath, 'publisher/sourcing/opportunities');\n\n const load = useCallback(\n async (initializeForm = false) => {\n if (!isEligible || !requestId) return;\n setLoading(true);\n setError(null);\n try {\n const result = await getOpportunity(requestId);\n setOpportunity(result.opportunity);\n setOffer(result.offer);\n setIsOpen(result.isOpen);\n if (!result.opportunity) {\n setError('This sourcing opportunity is no longer available.');\n } else if (initializeForm) {\n setForm({\n unitPrice: result.offer?.unitPrice ?? '',\n deliveryDate: result.offer\n ? toDateTimeLocal(result.offer.deliveryDate)\n : toDateTimeLocal(result.opportunity.deliveryWindowStart),\n storeProductId: result.offer?.storeProductId ?? '',\n message: result.offer?.message ?? '',\n });\n }\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'The sourcing opportunity could not be loaded.'));\n } finally {\n setLoading(false);\n }\n },\n [getOpportunity, isEligible, requestId]\n );\n\n useEffect(() => {\n void load(true);\n }, [load]);\n\n const publishedProducts = useMemo(\n () =>\n [...products]\n .filter((product) => product.status === 'PUBLISHED')\n .sort((left, right) => left.name.localeCompare(right.name)),\n [products]\n );\n\n const updateField =\n (field: OfferField) =>\n (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {\n setForm((current) => ({ ...current, [field]: event.target.value }));\n setErrors((current) => ({ ...current, [field]: undefined }));\n setSuccess(null);\n };\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!opportunity || !requestId || !isOpen || submitting) return;\n\n const validationErrors = validateOffer(form, opportunity);\n setErrors(validationErrors);\n setSubmitError(null);\n setSuccess(null);\n if (Object.keys(validationErrors).length > 0) return;\n\n const input: UpsertSourcingOfferInput = {\n requestId,\n quantity: opportunity.quantity,\n unit: opportunity.unit,\n unitPrice: form.unitPrice.trim(),\n currency: 'USD',\n deliveryDate: new Date(form.deliveryDate).toISOString(),\n storeProductId: form.storeProductId || undefined,\n message: form.message.trim() || undefined,\n };\n\n setSubmitting(true);\n try {\n const saved = await upsertOffer(input);\n setSuccess(offer ? 'Your offer was updated.' : 'Your offer was submitted.');\n setOffer(saved);\n await load(false);\n } catch (saveError) {\n setSubmitError(\n toErrorMessage(\n saveError,\n 'Your offer could not be saved. The request may have closed while you were editing.'\n )\n );\n } finally {\n setSubmitting(false);\n }\n };\n\n const fieldError = (field: OfferField) => {\n const message = errors[field];\n return message ? (\n <p id={`${field}-error`} className=\"mt-1 text-xs text-status-error-text\">\n {message}\n </p>\n ) : null;\n };\n\n const canEdit = isOpen && (!offer || offer.status === SourcingOfferStatus.Active);\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title={opportunity?.productName ?? 'Sourcing opportunity'}\n description=\"Review the sanitized buyer request and create or update your publisher’s offer.\"\n icon={<TicketCheck className=\"size-5\" aria-hidden=\"true\" />}\n onBack={() => navigate(opportunitiesPath)}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(joinStorePath(basePath, 'publisher/sourcing/offers'))}\n className={secondaryButtonClassName}\n >\n My offers\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Quantity and unit are fixed by the buyer request. Store validates publisher identity, USD\n settlement price, delivery window, and optional linked product on every save. You can\n update one active offer until the request closes.\n </PagePurpose>\n\n {accessLoading ? (\n <SourcingLoadingState label=\"Checking Store Publisher access…\" />\n ) : accessError ? (\n <SourcingErrorState\n title=\"Publisher access could not be checked\"\n message={accessError}\n onRetry={() => void refreshAccess()}\n />\n ) : !profile ? (\n <ContextRequiredPanel\n title=\"Store Publisher profile required\"\n description=\"Only users who own a verified Store Publisher profile can view this opportunity or submit an offer.\"\n />\n ) : !isEligible ? (\n <ContextRequiredPanel\n title=\"Verified publisher status required\"\n description={`The publisher profile “${profile.name}” is currently ${profile.verificationStatus.toLowerCase().replaceAll('_', ' ')}.`}\n />\n ) : loading && !opportunity ? (\n <SourcingLoadingState label=\"Loading opportunity and your offer…\" />\n ) : error && !opportunity ? (\n <SourcingErrorState\n title=\"Opportunity unavailable\"\n message={error}\n onRetry={requestId ? () => void load(true) : undefined}\n />\n ) : opportunity ? (\n <>\n {submitError ? (\n <div\n role=\"alert\"\n className=\"mb-5 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {submitError}\n </div>\n ) : null}\n {success ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"mb-5 flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\"\n >\n <Check className=\"size-4\" aria-hidden=\"true\" />\n {success}\n </div>\n ) : null}\n\n {offer?.order ? (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-sm font-semibold text-status-success-text\">\n This offer was accepted\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Store order {offer.order.id} ·{' '}\n {formatMoney(offer.order.total, offer.order.currency)} · {offer.order.status}\n </p>\n </div>\n {offer.order.fulfillment ? (\n <span className=\"rounded-full border border-border-default bg-surface-sunken px-3 py-1.5 text-xs font-medium text-text-secondary\">\n Fulfillment {offer.order.fulfillment.status.replaceAll('_', ' ')}\n </span>\n ) : null}\n </div>\n </EmphasisPanel>\n ) : null}\n\n <div className=\"grid gap-6 lg:grid-cols-[minmax(0,1.2fr)_minmax(340px,0.8fr)]\">\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <p className=\"text-sm font-medium text-accent-primary\">\n {opportunity.category}\n </p>\n <h2 className=\"mt-1 text-xl font-semibold text-text-primary\">\n {opportunity.productName}\n </h2>\n </div>\n <span\n className={`rounded-full border px-2.5 py-1 text-xs font-semibold ${\n isOpen\n ? 'border-status-success-border bg-status-success-bg-subtle text-status-success-text'\n : 'border-border-default bg-surface-sunken text-text-secondary'\n }`}\n >\n {isOpen ? 'OPEN' : 'CLOSED'}\n </span>\n </div>\n\n {opportunity.summary ? (\n <p className=\"mt-4 whitespace-pre-wrap text-sm leading-6 text-text-secondary\">\n {opportunity.summary}\n </p>\n ) : null}\n\n <dl className=\"mt-6 grid gap-5 border-t border-border-subtle pt-5 sm:grid-cols-2\">\n <div className=\"flex gap-3\">\n <PackageSearch\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Required quantity</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {opportunity.quantity.toLocaleString()}{' '}\n {formatSourcingUnit(opportunity.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <CircleDollarSign\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Buyer target</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatMoney(opportunity.targetUnitPrice, opportunity.targetCurrency)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <CalendarDays\n className=\"mt-0.5 size-5 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Delivery window</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDateTime(opportunity.deliveryWindowStart)} –{' '}\n {formatDateTime(opportunity.deliveryWindowEnd)}\n </dd>\n </div>\n </div>\n <div className=\"flex gap-3\">\n <MapPin className=\"mt-0.5 size-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n <div>\n <dt className=\"text-xs text-text-muted\">Destination</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatLocation({\n country: opportunity.deliveryCountry,\n region: opportunity.deliveryRegion,\n city: opportunity.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n </dl>\n </GlassCard>\n\n {canEdit ? (\n <form onSubmit={(event) => void handleSubmit(event)} noValidate>\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {offer ? 'Update your offer' : 'Create an offer'}\n </h2>\n <p className=\"mt-1 text-xs text-text-muted\">Offering as {profile.name}</p>\n </div>\n <BadgeCheck\n className=\"size-5 text-accent-primary\"\n aria-label=\"Verified publisher\"\n />\n </div>\n\n <div className=\"mt-5 space-y-5\">\n <label className={labelClassName}>\n Unit price\n <div className=\"mt-1.5 grid grid-cols-[1fr_96px] gap-2\">\n <input\n type=\"text\"\n value={form.unitPrice}\n onChange={updateField('unitPrice')}\n inputMode=\"decimal\"\n required\n maxLength={19}\n aria-invalid={Boolean(errors.unitPrice)}\n aria-describedby={\n errors.unitPrice ? 'unitPrice-error' : 'unitPrice-help'\n }\n placeholder=\"125.50\"\n className={`${inputClassName} mt-0`}\n />\n <span\n aria-label=\"Offer currency\"\n className=\"mt-0 flex items-center justify-center rounded-md border border-border-default bg-surface-sunken px-3 text-sm font-semibold text-text-secondary\"\n >\n USD\n </span>\n </div>\n {fieldError('unitPrice')}\n {!errors.unitPrice ? (\n <p id=\"unitPrice-help\" className={helpClassName}>\n Price per {formatSourcingUnit(opportunity.unit).toLowerCase()}. Store\n sourcing currently settles in USD.\n </p>\n ) : null}\n </label>\n\n <label className={labelClassName}>\n Promised delivery date\n <input\n type=\"datetime-local\"\n value={form.deliveryDate}\n onChange={updateField('deliveryDate')}\n required\n min={toDateTimeLocal(opportunity.deliveryWindowStart)}\n max={toDateTimeLocal(opportunity.deliveryWindowEnd)}\n aria-invalid={Boolean(errors.deliveryDate)}\n aria-describedby={\n errors.deliveryDate ? 'deliveryDate-error' : 'deliveryDate-help'\n }\n className={inputClassName}\n />\n {fieldError('deliveryDate')}\n {!errors.deliveryDate ? (\n <p id=\"deliveryDate-help\" className={helpClassName}>\n Must fall inside the buyer’s delivery window.\n </p>\n ) : null}\n </label>\n\n <label className={labelClassName}>\n Link a published product{' '}\n <span className=\"font-normal text-text-muted\">(optional)</span>\n <select\n value={form.storeProductId}\n onChange={updateField('storeProductId')}\n disabled={productsLoading}\n className={`${inputClassName} scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary`}\n aria-describedby={\n productsError ? 'store-product-error' : 'store-product-help'\n }\n >\n <option value=\"\">\n {productsLoading\n ? 'Loading published products…'\n : 'No linked Store product'}\n </option>\n {publishedProducts.map((product) => (\n <option key={product.id} value={product.id}>\n {product.name}\n </option>\n ))}\n </select>\n {productsError ? (\n <div\n id=\"store-product-error\"\n role=\"alert\"\n className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-status-error-text\"\n >\n <span>{productsError}</span>\n <button\n type=\"button\"\n onClick={() => void refreshProducts()}\n className=\"font-semibold underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg\"\n >\n Try again\n </button>\n </div>\n ) : (\n <p id=\"store-product-help\" className={helpClassName}>\n Only products owned by this publisher and currently published are\n accepted. The link is reference metadata and does not reserve catalog\n inventory or create digital entitlements.\n </p>\n )}\n </label>\n\n <label className={labelClassName}>\n Message <span className=\"font-normal text-text-muted\">(optional)</span>\n <textarea\n value={form.message}\n onChange={updateField('message')}\n maxLength={1000}\n rows={4}\n aria-invalid={Boolean(errors.message)}\n aria-describedby={errors.message ? 'message-error' : 'message-help'}\n placeholder=\"Add packaging, quality, or delivery notes relevant to this offer.\"\n className={`${inputClassName} resize-y`}\n />\n {fieldError('message')}\n {!errors.message ? (\n <p id=\"message-help\" className={helpClassName}>\n {form.message.length}/1,000 characters\n </p>\n ) : null}\n </label>\n </div>\n\n <button\n type=\"submit\"\n disabled={submitting}\n className={`${primaryButtonClassName} mt-6 w-full`}\n >\n {submitting ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : offer ? (\n <Save className=\"size-4\" aria-hidden=\"true\" />\n ) : (\n <TicketCheck className=\"size-4\" aria-hidden=\"true\" />\n )}\n {submitting ? 'Saving offer…' : offer ? 'Update offer' : 'Submit offer'}\n </button>\n </GlassCard>\n </form>\n ) : offer ? (\n <GlassCard className=\"p-5 sm:p-6\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Your offer</h2>\n <p className=\"mt-1 text-xs text-text-muted\">\n Updated {formatDateTime(offer.updatedAt)}\n </p>\n </div>\n <SourcingStatusBadge status={offer.status} />\n </div>\n <dl className=\"mt-5 space-y-4 text-sm\">\n <div>\n <dt className=\"text-xs text-text-muted\">Unit price</dt>\n <dd className=\"mt-0.5 text-lg font-semibold text-text-primary\">\n {formatMoney(offer.unitPrice, offer.currency)}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs text-text-muted\">Promised delivery</dt>\n <dd className=\"mt-0.5 font-medium text-text-primary\">\n {formatDateTime(offer.deliveryDate)}\n </dd>\n </div>\n {offer.message ? (\n <div>\n <dt className=\"text-xs text-text-muted\">Message</dt>\n <dd className=\"mt-1 whitespace-pre-wrap text-text-secondary\">\n {offer.message}\n </dd>\n </div>\n ) : null}\n </dl>\n </GlassCard>\n ) : (\n <ContextRequiredPanel\n title=\"This request is closed\"\n description=\"The buyer matched or canceled this request before your publisher submitted an offer.\"\n />\n )}\n </div>\n </>\n ) : null}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -73,7 +73,7 @@ var P = 30, F = () => {
|
|
|
73
73
|
}), /* @__PURE__ */ j("select", {
|
|
74
74
|
value: V,
|
|
75
75
|
onChange: (e) => H(e.target.value),
|
|
76
|
-
className: "mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20",
|
|
76
|
+
className: "mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
77
77
|
children: [
|
|
78
78
|
/* @__PURE__ */ A("option", {
|
|
79
79
|
value: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourcingRequestsPage.js","names":[],"sources":["../../src/pages/SourcingRequestsPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { FC } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n ArrowRight,\n CalendarDays,\n FilePlus2,\n FileText,\n MapPin,\n PackageSearch,\n RefreshCw,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingRequestStatus } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport { type BuyerSourcingRequestListItem, useSourcingApi } from '../hooks/useSourcing';\nimport {\n ContextRequiredPanel,\n formatDate,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n joinStorePath,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingEmptyState,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\nconst PAGE_SIZE = 30;\n\ntype RequestFilter = SourcingRequestStatus | '';\n\nexport const SourcingRequestsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { hasOrganizationContext, listBuyerRequests } = useSourcingApi();\n const [requests, setRequests] = useState<BuyerSourcingRequestListItem[]>([]);\n const [status, setStatus] = useState<RequestFilter>('');\n const [offset, setOffset] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(\n async (nextOffset = 0, append = false) => {\n if (!hasOrganizationContext) {\n setRequests([]);\n setHasMore(false);\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n try {\n const result = await listBuyerRequests({\n status: status || undefined,\n limit: PAGE_SIZE,\n offset: nextOffset,\n });\n setRequests((current) => (append ? [...current, ...result] : result));\n setOffset(nextOffset);\n setHasMore(result.length === PAGE_SIZE);\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'Failed to load sourcing requests.'));\n } finally {\n setLoading(false);\n }\n },\n [hasOrganizationContext, listBuyerRequests, status]\n );\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const counts = useMemo(\n () =>\n requests.reduce(\n (result, request) => {\n result.total += 1;\n result.offers += request.offers.filter((offer) => offer.status === 'ACTIVE').length;\n return result;\n },\n { total: 0, offers: 0 }\n ),\n [requests]\n );\n\n const createPath = joinStorePath(basePath, 'sourcing/requests/new');\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title=\"Sourcing requests\"\n description=\"Request products from verified Store publishers and compare private offers for your organization.\"\n icon={<PackageSearch className=\"size-5\" aria-hidden=\"true\" />}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(createPath)}\n disabled={!hasOrganizationContext}\n className={primaryButtonClassName}\n >\n <FilePlus2 className=\"size-4\" aria-hidden=\"true\" />\n New request\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Create one request for the exact quantity, delivery window, and destination you need.\n Offers remain private to your organization. Accepting one offer creates a Store order and\n closes the request; all other offers are rejected automatically.\n </PagePurpose>\n\n {!hasOrganizationContext ? (\n <ContextRequiredPanel\n title=\"Choose an organization\"\n description=\"Sourcing requests use your active global organization and its billing account. Choose an organization in the app switcher; a farm workspace is not required.\"\n />\n ) : (\n <>\n <div className=\"mb-5 flex flex-col gap-3 rounded-xl border border-border-subtle bg-surface-elevated p-4 sm:flex-row sm:items-end sm:justify-between\">\n <label className=\"block sm:max-w-xs sm:flex-1\">\n <span className=\"text-sm font-medium text-text-primary\">Request status</span>\n <select\n value={status}\n onChange={(event) => setStatus(event.target.value as RequestFilter)}\n className=\"mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20\"\n >\n <option value=\"\">All statuses</option>\n <option value={SourcingRequestStatus.Open}>Open</option>\n <option value={SourcingRequestStatus.Matched}>Matched</option>\n <option value={SourcingRequestStatus.Canceled}>Canceled</option>\n </select>\n </label>\n <div className=\"flex flex-wrap items-center gap-3 text-sm text-text-secondary\">\n <span>{counts.total} shown</span>\n <span aria-hidden=\"true\">·</span>\n <span>{counts.offers} active offers</span>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n <RefreshCw\n className={`size-4 ${loading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n Refresh\n </button>\n </div>\n </div>\n\n {loading && requests.length === 0 ? (\n <SourcingLoadingState label=\"Loading sourcing requests…\" />\n ) : error && requests.length === 0 ? (\n <SourcingErrorState\n title=\"Sourcing requests could not be loaded\"\n message={error}\n onRetry={() => void load()}\n />\n ) : requests.length === 0 ? (\n <SourcingEmptyState\n title={status ? 'No requests match this status' : 'No sourcing requests yet'}\n description={\n status\n ? 'Choose another status or create a new request.'\n : 'Describe what your organization needs and verified publishers can respond with offers.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigate(createPath)}\n className={primaryButtonClassName}\n >\n <FilePlus2 className=\"size-4\" aria-hidden=\"true\" />\n Create request\n </button>\n }\n />\n ) : (\n <>\n {error ? (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {error}\n </div>\n ) : null}\n <ul className=\"grid gap-4 md:grid-cols-2\">\n {requests.map((request) => {\n const activeOffers = request.offers.filter(\n (offer) => offer.status === 'ACTIVE'\n ).length;\n return (\n <li key={request.id}>\n <GlassCard className=\"flex h-full flex-col p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 text-xs font-medium text-accent-primary\">\n <FileText className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"truncate\">{request.category}</span>\n </div>\n <h2 className=\"mt-2 line-clamp-2 text-lg font-semibold text-text-primary\">\n {request.productName}\n </h2>\n </div>\n <SourcingStatusBadge status={request.status} />\n </div>\n\n <dl className=\"mt-5 grid gap-3 text-sm sm:grid-cols-2\">\n <div className=\"flex items-start gap-2\">\n <PackageSearch\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Quantity</dt>\n <dd className=\"font-medium text-text-primary\">\n {request.quantity.toLocaleString()}{' '}\n {formatSourcingUnit(request.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex items-start gap-2\">\n <MapPin\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Deliver to</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatLocation({\n country: request.deliveryCountry,\n region: request.deliveryRegion,\n city: request.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n <div className=\"flex items-start gap-2 sm:col-span-2\">\n <CalendarDays\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Delivery window</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(request.deliveryWindowStart)} –{' '}\n {formatDate(request.deliveryWindowEnd)}\n </dd>\n </div>\n </div>\n </dl>\n\n <div className=\"mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-border-subtle pt-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {activeOffers} active {activeOffers === 1 ? 'offer' : 'offers'}\n </p>\n <p className=\"text-xs text-text-muted\">\n Target{' '}\n {formatMoney(request.targetUnitPrice, request.targetCurrency)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() =>\n navigate(joinStorePath(basePath, `sourcing/requests/${request.id}`))\n }\n className={secondaryButtonClassName}\n >\n View details\n <ArrowRight className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </GlassCard>\n </li>\n );\n })}\n </ul>\n\n {hasMore ? (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={() => void load(offset + PAGE_SIZE, true)}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </div>\n ) : null}\n </>\n )}\n </>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SourcingRequestsPage.js","names":[],"sources":["../../src/pages/SourcingRequestsPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { FC } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n ArrowRight,\n CalendarDays,\n FilePlus2,\n FileText,\n MapPin,\n PackageSearch,\n RefreshCw,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { SourcingRequestStatus } from '../generated/global-types';\nimport { useStore } from '../providers/StoreProvider';\nimport { type BuyerSourcingRequestListItem, useSourcingApi } from '../hooks/useSourcing';\nimport {\n ContextRequiredPanel,\n formatDate,\n formatLocation,\n formatMoney,\n formatSourcingUnit,\n joinStorePath,\n primaryButtonClassName,\n secondaryButtonClassName,\n SourcingEmptyState,\n SourcingErrorState,\n SourcingLoadingState,\n SourcingPageHeader,\n SourcingStatusBadge,\n toErrorMessage,\n} from '../components/sourcing/SourcingUi';\n\nconst PAGE_SIZE = 30;\n\ntype RequestFilter = SourcingRequestStatus | '';\n\nexport const SourcingRequestsPage: FC = () => {\n const navigate = useNavigate();\n const { basePath } = useStore();\n const { hasOrganizationContext, listBuyerRequests } = useSourcingApi();\n const [requests, setRequests] = useState<BuyerSourcingRequestListItem[]>([]);\n const [status, setStatus] = useState<RequestFilter>('');\n const [offset, setOffset] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(\n async (nextOffset = 0, append = false) => {\n if (!hasOrganizationContext) {\n setRequests([]);\n setHasMore(false);\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n try {\n const result = await listBuyerRequests({\n status: status || undefined,\n limit: PAGE_SIZE,\n offset: nextOffset,\n });\n setRequests((current) => (append ? [...current, ...result] : result));\n setOffset(nextOffset);\n setHasMore(result.length === PAGE_SIZE);\n } catch (loadError) {\n setError(toErrorMessage(loadError, 'Failed to load sourcing requests.'));\n } finally {\n setLoading(false);\n }\n },\n [hasOrganizationContext, listBuyerRequests, status]\n );\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const counts = useMemo(\n () =>\n requests.reduce(\n (result, request) => {\n result.total += 1;\n result.offers += request.offers.filter((offer) => offer.status === 'ACTIVE').length;\n return result;\n },\n { total: 0, offers: 0 }\n ),\n [requests]\n );\n\n const createPath = joinStorePath(basePath, 'sourcing/requests/new');\n\n return (\n <div className=\"min-h-full bg-surface\">\n <SourcingPageHeader\n title=\"Sourcing requests\"\n description=\"Request products from verified Store publishers and compare private offers for your organization.\"\n icon={<PackageSearch className=\"size-5\" aria-hidden=\"true\" />}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(createPath)}\n disabled={!hasOrganizationContext}\n className={primaryButtonClassName}\n >\n <FilePlus2 className=\"size-4\" aria-hidden=\"true\" />\n New request\n </button>\n }\n />\n\n <main className=\"mx-auto max-w-6xl px-4 py-6 sm:px-6\">\n <PagePurpose className=\"mb-6\">\n Create one request for the exact quantity, delivery window, and destination you need.\n Offers remain private to your organization. Accepting one offer creates a Store order and\n closes the request; all other offers are rejected automatically.\n </PagePurpose>\n\n {!hasOrganizationContext ? (\n <ContextRequiredPanel\n title=\"Choose an organization\"\n description=\"Sourcing requests use your active global organization and its billing account. Choose an organization in the app switcher; a farm workspace is not required.\"\n />\n ) : (\n <>\n <div className=\"mb-5 flex flex-col gap-3 rounded-xl border border-border-subtle bg-surface-elevated p-4 sm:flex-row sm:items-end sm:justify-between\">\n <label className=\"block sm:max-w-xs sm:flex-1\">\n <span className=\"text-sm font-medium text-text-primary\">Request status</span>\n <select\n value={status}\n onChange={(event) => setStatus(event.target.value as RequestFilter)}\n className=\"mt-1.5 w-full rounded-lg border border-border-default bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg focus:ring-2 focus:ring-action-primary-bg/20 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">All statuses</option>\n <option value={SourcingRequestStatus.Open}>Open</option>\n <option value={SourcingRequestStatus.Matched}>Matched</option>\n <option value={SourcingRequestStatus.Canceled}>Canceled</option>\n </select>\n </label>\n <div className=\"flex flex-wrap items-center gap-3 text-sm text-text-secondary\">\n <span>{counts.total} shown</span>\n <span aria-hidden=\"true\">·</span>\n <span>{counts.offers} active offers</span>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n <RefreshCw\n className={`size-4 ${loading ? 'animate-spin' : ''}`}\n aria-hidden=\"true\"\n />\n Refresh\n </button>\n </div>\n </div>\n\n {loading && requests.length === 0 ? (\n <SourcingLoadingState label=\"Loading sourcing requests…\" />\n ) : error && requests.length === 0 ? (\n <SourcingErrorState\n title=\"Sourcing requests could not be loaded\"\n message={error}\n onRetry={() => void load()}\n />\n ) : requests.length === 0 ? (\n <SourcingEmptyState\n title={status ? 'No requests match this status' : 'No sourcing requests yet'}\n description={\n status\n ? 'Choose another status or create a new request.'\n : 'Describe what your organization needs and verified publishers can respond with offers.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigate(createPath)}\n className={primaryButtonClassName}\n >\n <FilePlus2 className=\"size-4\" aria-hidden=\"true\" />\n Create request\n </button>\n }\n />\n ) : (\n <>\n {error ? (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text\"\n >\n {error}\n </div>\n ) : null}\n <ul className=\"grid gap-4 md:grid-cols-2\">\n {requests.map((request) => {\n const activeOffers = request.offers.filter(\n (offer) => offer.status === 'ACTIVE'\n ).length;\n return (\n <li key={request.id}>\n <GlassCard className=\"flex h-full flex-col p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 text-xs font-medium text-accent-primary\">\n <FileText className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"truncate\">{request.category}</span>\n </div>\n <h2 className=\"mt-2 line-clamp-2 text-lg font-semibold text-text-primary\">\n {request.productName}\n </h2>\n </div>\n <SourcingStatusBadge status={request.status} />\n </div>\n\n <dl className=\"mt-5 grid gap-3 text-sm sm:grid-cols-2\">\n <div className=\"flex items-start gap-2\">\n <PackageSearch\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Quantity</dt>\n <dd className=\"font-medium text-text-primary\">\n {request.quantity.toLocaleString()}{' '}\n {formatSourcingUnit(request.unit)}\n </dd>\n </div>\n </div>\n <div className=\"flex items-start gap-2\">\n <MapPin\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Deliver to</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatLocation({\n country: request.deliveryCountry,\n region: request.deliveryRegion,\n city: request.deliveryCity,\n })}\n </dd>\n </div>\n </div>\n <div className=\"flex items-start gap-2 sm:col-span-2\">\n <CalendarDays\n className=\"mt-0.5 size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs text-text-muted\">Delivery window</dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(request.deliveryWindowStart)} –{' '}\n {formatDate(request.deliveryWindowEnd)}\n </dd>\n </div>\n </div>\n </dl>\n\n <div className=\"mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-border-subtle pt-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {activeOffers} active {activeOffers === 1 ? 'offer' : 'offers'}\n </p>\n <p className=\"text-xs text-text-muted\">\n Target{' '}\n {formatMoney(request.targetUnitPrice, request.targetCurrency)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() =>\n navigate(joinStorePath(basePath, `sourcing/requests/${request.id}`))\n }\n className={secondaryButtonClassName}\n >\n View details\n <ArrowRight className=\"size-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </GlassCard>\n </li>\n );\n })}\n </ul>\n\n {hasMore ? (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={() => void load(offset + PAGE_SIZE, true)}\n disabled={loading}\n className={secondaryButtonClassName}\n >\n {loading ? 'Loading…' : 'Load more'}\n </button>\n </div>\n ) : null}\n </>\n )}\n </>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|