@aglyn/plugins-commerce 1.0.0-beta.234 → 1.0.0-beta.236
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/package.json +13 -13
- package/src/lib/components/cart.js +3 -2
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +3 -6
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/commerce-glance-card.component.js +9 -3
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -1
- package/src/lib/components/console/customer-notifications-card.component.js +3 -1
- package/src/lib/components/console/customer-notifications-card.component.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +2 -3
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +3 -4
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.js +2 -2
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
- package/src/lib/components/console/locations-card.component.js +1 -1
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +2 -2
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +35 -30
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-fulfillment-panel.component.d.ts +11 -1
- package/src/lib/components/console/order-fulfillment-panel.component.js +75 -4
- package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
- package/src/lib/components/console/order-zones.d.ts +45 -1
- package/src/lib/components/console/order-zones.js +8 -0
- package/src/lib/components/console/order-zones.js.map +1 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +15 -1
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +3 -2
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.d.ts +2 -0
- package/src/lib/components/console/product-editor-dialog.component.js +30 -12
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +13 -4
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +2 -1
- package/src/lib/components/console/products-hub-zone.component.js +1 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/reviews-moderation-card.component.js +1 -2
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +2 -2
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
- package/src/lib/components/console/shipstation-card.component.js +1 -2
- package/src/lib/components/console/shipstation-card.component.js.map +1 -1
- package/src/lib/components/console/store-settings-card.component.js +3 -1
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/order-status.js +24 -1
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +30 -16
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +7 -0
- package/src/lib/components/product-grid.js +50 -7
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/components/related-products.js +1 -1
- package/src/lib/components/related-products.js.map +1 -1
- package/src/lib/components/wishlist.js +1 -1
- package/src/lib/components/wishlist.js.map +1 -1
- package/src/lib/declarations.server.js +11 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +8 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce.d.ts +21 -0
- package/src/lib/model/commerce.js +39 -1
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/order-annotations.d.ts +54 -0
- package/src/lib/model/order-annotations.js +76 -0
- package/src/lib/model/order-annotations.js.map +1 -0
- package/src/lib/model/order-events.d.ts +2 -0
- package/src/lib/model/order-events.js.map +1 -1
- package/src/lib/model/order-fulfillment.d.ts +29 -0
- package/src/lib/model/order-fulfillment.js +63 -0
- package/src/lib/model/order-fulfillment.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +36 -0
- package/src/lib/model/order-local-fulfillment.js +19 -1
- package/src/lib/model/order-local-fulfillment.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +11 -0
- package/src/lib/model/order-status-view.js +20 -3
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/model/product-write.js +8 -4
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/server/api-v1/openapi.js +1 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +1 -0
- package/src/lib/server/api-v1/order-view.js +10 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/cart-checkout.js +9 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +3 -1
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout.js +11 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +12 -0
- package/src/lib/server/fulfill-order.js +13 -0
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/local-delivery-records.d.ts +30 -0
- package/src/lib/server/local-delivery-records.js +284 -0
- package/src/lib/server/local-delivery-records.js.map +1 -0
- package/src/lib/server/order-annotations.d.ts +28 -0
- package/src/lib/server/order-annotations.js +143 -0
- package/src/lib/server/order-annotations.js.map +1 -0
- package/src/lib/server/order-events.js +2 -1
- package/src/lib/server/order-events.js.map +1 -1
- package/src/lib/server/order-notifications.js +39 -8
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-payment.js +9 -3
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/product-catalog.js +6 -2
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product-drafts.d.ts +36 -9
- package/src/lib/server/product-drafts.js +24 -5
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product.d.ts +6 -1
- package/src/lib/server/product.js +1 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.d.ts +29 -0
- package/src/lib/server/products-write.js +7 -7
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/shipment-records.js +2 -0
- package/src/lib/server/shipment-records.js.map +1 -1
- package/src/lib/server/site-page-resolver.d.ts +13 -0
- package/src/lib/server/site-page-resolver.js +138 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +5 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/utils/use-storefront-purchase-event.js +8 -2
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/local-delivery-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Divider,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { collection, doc, limit, query, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocalDeliverySettingsCardProps {\n hostId: string\n}\n\nconst LEAD_TIMES: ReadonlyArray<[number, string]> = [\n [0, 'No notice'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n]\n\n/**\n * Local delivery (AGL-3624): the store's OWN delivery — zones by postal code\n * or by distance, each with a fee, a minimum order and a free-over\n * threshold, and the delivery windows a buyer books at the cart. Stored on\n * `settings/store.localDelivery`, written whole so a cleared field is\n * cleared. Marketplace couriers (DoorDash and the rest) are the Delivery apps\n * plugin's, and nothing here touches them.\n *\n * DISTANCE ZONES are offered only where the store's shipping address check\n * can place an address on a map (`GET /api/commerce/local-fulfillment`), and\n * need the store placed first (\"Place on map\"). Postal-code zones need\n * nothing.\n */\nexport function LocalDeliverySettingsCard(props: LocalDeliverySettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { data: store, status: storeStatus, fromCache } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<CommerceModel.LocalDeliverySettings | null>(null)\n const current: CommerceModel.LocalDeliverySettings = draft ?? (store?.localDelivery as CommerceModel.LocalDeliverySettings) ?? {}\n const update = (patch: Partial<CommerceModel.LocalDeliverySettings>) => setDraft({ ...current, ...patch })\n const [radiusAvailable, setRadiusAvailable] = useState(false)\n useEffect(() => {\n let live = true\n authorizedFetch(user, `/api/commerce/local-fulfillment?hostId=${encodeURIComponent(hostId)}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((answer) => {\n if (live) setRadiusAvailable(Boolean(answer?.radius))\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n }, [hostId, user])\n\n const zones = current.zones ?? []\n const updateZone = (index: number, patch: Partial<CommerceModel.LocalDeliveryZone> | null) => {\n const next = [...zones]\n if (patch === null) next.splice(index, 1)\n else next[index] = { ...next[index], ...patch }\n update({ zones: next })\n }\n const addZone = () =>\n update({\n zones: [\n ...zones,\n { id: Aglyn.createResourceUid(), name: 'Local delivery', kind: 'postcode', postcodes: [], feeCents: 0 },\n ],\n })\n\n const [locating, setLocating] = useState(false)\n const handleLocate = useCallback(async () => {\n if (!current.locationId) {\n return void enqueueSnackbar('Choose the location deliveries leave from first', { variant: 'info', persist: false })\n }\n setLocating(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/local-fulfillment', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'locate', locationId: current.locationId }),\n })\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.origin) {\n enqueueSnackbar(payload?.error ?? 'Could not place the store on a map', { variant: 'warning', persist: false })\n return\n }\n setDraft({ ...current, origin: payload.origin })\n } finally {\n setLocating(false)\n }\n }, [current, enqueueSnackbar, hostId, user])\n\n const badWindowLines = invalidOpeningHoursLines(current.windows ?? '')\n const problems = CommerceModel.localDeliveryProblems(CommerceModel.normalizeLocalDeliverySettings(current))\n\n const handleSave = useCallback(async () => {\n const value = CommerceModel.normalizeLocalDeliverySettings(current)\n const verdict = await writeGuardedBySeed(\n { subject: 'local delivery settings', unreadable: storeStatus === 'error', fromCache },\n async () => {\n // `mergeFields` replaces `localDelivery` whole and leaves every other\n // map on the settings document alone.\n await setDoc(doc(firestore, 'hosts', hostId, 'settings', 'store'), { localDelivery: value }, {\n mergeFields: ['localDelivery'],\n })\n },\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n }\n setDraft(null)\n enqueueSnackbar('Local delivery saved', { variant: 'success', persist: false })\n }, [current, enqueueSnackbar, firestore, fromCache, hostId, storeStatus])\n\n const dollars = (cents: number | undefined) => (cents ? String(cents / 100) : '')\n const cents = (raw: string) => (raw.trim() === '' ? 0 : Math.max(0, Math.round(Number(raw) * 100) || 0))\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Local delivery'}\n help={pluginDocsHelp('commerce', { anchor: '#pickup-and-local-delivery' })}\n HeaderProps={{\n action: (\n <Button variant=\"contained\" size=\"small\" disabled={!draft || badWindowLines.length > 0} onClick={handleSave}>\n {'Save'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(current.enabled)}\n onChange={(event) => update({ enabled: event.target.checked })}\n />\n }\n label=\"Deliver orders yourself\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose local delivery at the cart, enter their postal code and book a delivery time. ' +\n 'You mark each order out for delivery and delivered, and the buyer is told at each step.'}\n </Typography>\n {problems.length ? (\n <Alert severity=\"warning\">{`Checkout won’t offer delivery yet: ${problems.join(' ')}`}</Alert>\n ) : null}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Country\"\n value={current.country ?? ''}\n onChange={(event) => update({ country: event.target.value.toUpperCase().slice(0, 2) })}\n size=\"small\"\n placeholder=\"US\"\n helperText=\"Two letters\"\n sx={{ width: { sm: 120 } }}\n />\n <TextField\n select\n label=\"Leaves from\"\n value={current.locationId ?? ''}\n onChange={(event) => update({ locationId: event.target.value || undefined })}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText=\"Its stock is reserved and sold for delivery orders\"\n >\n <MenuItem value=\"\">{'No particular location'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Zones'}</Typography>\n {zones.map((zone, index) => (\n <Stack key={zone.id} spacing={1}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Zone name\"\n value={zone.name}\n onChange={(event) => updateZone(index, { name: event.target.value })}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {radiusAvailable || zone.kind === 'radius' ? (\n <TextField\n select\n label=\"Matched by\"\n value={zone.kind}\n onChange={(event) =>\n updateZone(index, { kind: event.target.value as CommerceModel.LocalDeliveryZone['kind'] })\n }\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n >\n <MenuItem value=\"postcode\">{'Postal codes'}</MenuItem>\n <MenuItem value=\"radius\">{'Distance'}</MenuItem>\n </TextField>\n ) : null}\n <Button size=\"small\" color=\"error\" onClick={() => updateZone(index, null)}>\n {'Remove'}\n </Button>\n </Stack>\n {zone.kind === 'radius' ? (\n <TextField\n label=\"Within (km)\"\n type=\"number\"\n value={zone.radiusKm ?? ''}\n onChange={(event) => updateZone(index, { radiusKm: Number(event.target.value) || 0 })}\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n />\n ) : (\n <TextField\n label=\"Postal codes\"\n value={(zone.postcodes ?? []).join(', ')}\n onChange={(event) =>\n updateZone(index, {\n postcodes: event.target.value.split(',').map((code) => code.trim()).filter(Boolean),\n })\n }\n size=\"small\"\n placeholder=\"10001, 100*, 10010-10020\"\n helperText=\"Exact codes, a prefix ending in *, or a range\"\n />\n )}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Fee ($)\"\n value={dollars(zone.feeCents)}\n onChange={(event) => updateZone(index, { feeCents: cents(event.target.value) })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Minimum order ($)\"\n value={dollars(zone.minimumCents)}\n onChange={(event) => updateZone(index, { minimumCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Free over ($)\"\n value={dollars(zone.freeOverCents)}\n onChange={(event) => updateZone(index, { freeOverCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Divider flexItem />\n </Stack>\n ))}\n <Button size=\"small\" sx={{ alignSelf: 'flex-start' }} onClick={addZone}>\n {'Add zone'}\n </Button>\n {zones.some((zone) => zone.kind === 'radius') ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {current.origin\n ? `Distances measure from ${current.origin.lat.toFixed(4)}, ${current.origin.lng.toFixed(4)}.`\n : 'Distance zones measure from the location deliveries leave from, once it is placed on a map.'}\n </Typography>\n <Button size=\"small\" disabled={locating} onClick={handleLocate}>\n {current.origin ? 'Place again' : 'Place on map'}\n </Button>\n </Stack>\n ) : null}\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Delivery times'}</Typography>\n <TextField\n label=\"Delivery windows\"\n value={current.windows ?? ''}\n onChange={(event) => update({ windows: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-12:00\\nMo-Fr 13:00-17:00'}\n error={badWindowLines.length > 0}\n helperText={\n badWindowLines.length\n ? `Line ${badWindowLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-12:00.`\n : 'One window per line. Each line is offered on each of its days.'\n }\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n select\n label=\"Order at least\"\n value={String(current.leadTimeMinutes ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES)}\n onChange={(event) => update({ leadTimeMinutes: Number(event.target.value) })}\n size=\"small\"\n helperText=\"Before a window starts\"\n sx={{ flex: 1 }}\n >\n {LEAD_TIMES.map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Book up to (days ahead)\"\n type=\"number\"\n value={current.daysAhead ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD}\n onChange={(event) =>\n update({\n daysAhead: Math.min(\n CommerceModel.LOCAL_DELIVERY_MAX_DAYS_AHEAD,\n Math.max(1, Math.round(Number(event.target.value)) || 1),\n ),\n })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"What buyers should know\"\n value={current.instructions ?? ''}\n onChange={(event) => update({ instructions: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"We text when we’re 10 minutes away.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n </Stack>\n </CardDisplay>\n )\n}\nLocalDeliverySettingsCard.displayName = 'LocalDeliverySettingsCard'\n\nexport default LocalDeliverySettingsCard\n"],"names":["Aglyn","invalidOpeningHoursLines","CommerceModel","CardDisplay","useSnackbar","Alert","Button","Divider","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","authorizedFetch","collection","doc","limit","query","setDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useUser","writeGuardedBySeed","pluginDocsHelp","LEAD_TIMES","LocalDeliverySettingsCard","props","draft","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","locationDocs","idField","setDraft","localDelivery","update","patch","radiusAvailable","setRadiusAvailable","live","encodeURIComponent","then","response","ok","json","answer","Boolean","radius","catch","undefined","zones","updateZone","index","next","splice","addZone","id","createResourceUid","name","kind","postcodes","feeCents","locating","setLocating","handleLocate","locationId","variant","persist","method","headers","body","JSON","stringify","action","payload","origin","error","badWindowLines","windows","problems","localDeliveryProblems","normalizeLocalDeliverySettings","handleSave","value","verdict","subject","unreadable","mergeFields","message","dollars","cents","String","raw","trim","Math","max","round","Number","locations","sort","a","b","localeCompare","header","help","anchor","HeaderProps","size","disabled","length","onClick","contentGutterX","contentGutterY","spacing","control","checked","enabled","onChange","event","target","label","color","severity","join","direction","xs","sm","country","toUpperCase","slice","placeholder","helperText","sx","width","select","flex","map","location","$id","zone","type","radiusKm","split","code","filter","slotProps","htmlInput","inputMode","minimumCents","freeOverCents","flexItem","alignSelf","some","alignItems","lat","toFixed","lng","multiline","minRows","leadTimeMinutes","LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES","minutes","daysAhead","LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD","min","LOCAL_DELIVERY_MAX_DAYS_AHEAD","instructions","maxLength","PICKUP_INSTRUCTIONS_MAX","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/local-delivery-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Divider,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { collection, doc, limit, query, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocalDeliverySettingsCardProps {\n hostId: string\n}\n\nconst LEAD_TIMES: ReadonlyArray<[number, string]> = [\n [0, 'No notice'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n]\n\n/**\n * Local delivery (AGL-3624): the store's OWN delivery — zones by postal code\n * or by distance, each with a fee, a minimum order and a free-over\n * threshold, and the delivery windows a buyer books at the cart. Stored on\n * `settings/store.localDelivery`, written whole so a cleared field is\n * cleared. Marketplace couriers (DoorDash and the rest) are the Delivery apps\n * plugin's, and nothing here touches them.\n *\n * DISTANCE ZONES are offered only where the store's shipping address check\n * can place an address on a map (`GET /api/commerce/local-fulfillment`), and\n * need the store placed first (\"Place on map\"). Postal-code zones need\n * nothing.\n */\nexport function LocalDeliverySettingsCard(props: LocalDeliverySettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { data: store, status: storeStatus, fromCache } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<CommerceModel.LocalDeliverySettings | null>(null)\n const current: CommerceModel.LocalDeliverySettings = draft ?? (store?.localDelivery as CommerceModel.LocalDeliverySettings) ?? {}\n const update = (patch: Partial<CommerceModel.LocalDeliverySettings>) => setDraft({ ...current, ...patch })\n const [radiusAvailable, setRadiusAvailable] = useState(false)\n useEffect(() => {\n let live = true\n authorizedFetch(user, `/api/commerce/local-fulfillment?hostId=${encodeURIComponent(hostId)}`)\n .then((response) => (response.ok ? response.json() : null))\n .then((answer) => {\n if (live) setRadiusAvailable(Boolean(answer?.radius))\n })\n .catch(() => undefined)\n return () => {\n live = false\n }\n }, [hostId, user])\n\n const zones = current.zones ?? []\n const updateZone = (index: number, patch: Partial<CommerceModel.LocalDeliveryZone> | null) => {\n const next = [...zones]\n if (patch === null) next.splice(index, 1)\n else next[index] = { ...next[index], ...patch }\n update({ zones: next })\n }\n const addZone = () =>\n update({\n zones: [\n ...zones,\n { id: Aglyn.createResourceUid(), name: 'Local delivery', kind: 'postcode', postcodes: [], feeCents: 0 },\n ],\n })\n\n const [locating, setLocating] = useState(false)\n const handleLocate = useCallback(async () => {\n if (!current.locationId) {\n return void enqueueSnackbar('Choose the location deliveries leave from first', { variant: 'info', persist: false })\n }\n setLocating(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/local-fulfillment', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'locate', locationId: current.locationId }),\n })\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok || !payload?.origin) {\n enqueueSnackbar(payload?.error ?? 'Could not place the store on a map', { variant: 'warning', persist: false })\n return\n }\n setDraft({ ...current, origin: payload.origin })\n } finally {\n setLocating(false)\n }\n }, [current, enqueueSnackbar, hostId, user])\n\n const badWindowLines = invalidOpeningHoursLines(current.windows ?? '')\n const problems = CommerceModel.localDeliveryProblems(CommerceModel.normalizeLocalDeliverySettings(current))\n\n const handleSave = useCallback(async () => {\n const value = CommerceModel.normalizeLocalDeliverySettings(current)\n const verdict = await writeGuardedBySeed(\n { subject: 'local delivery settings', unreadable: storeStatus === 'error', fromCache },\n async () => {\n // `mergeFields` replaces `localDelivery` whole and leaves every other\n // map on the settings document alone.\n await setDoc(doc(firestore, 'hosts', hostId, 'settings', 'store'), { localDelivery: value }, {\n mergeFields: ['localDelivery'],\n })\n },\n )\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n }\n setDraft(null)\n enqueueSnackbar('Local delivery saved', { variant: 'success', persist: false })\n }, [current, enqueueSnackbar, firestore, fromCache, hostId, storeStatus])\n\n const dollars = (cents: number | undefined) => (cents ? String(cents / 100) : '')\n const cents = (raw: string) => (raw.trim() === '' ? 0 : Math.max(0, Math.round(Number(raw) * 100) || 0))\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Local delivery'}\n help={pluginDocsHelp('pickupAndDelivery', {\n anchor: '#set-up-local-delivery',\n })}\n HeaderProps={{\n action: (\n <Button variant=\"contained\" size=\"small\" disabled={!draft || badWindowLines.length > 0} onClick={handleSave}>\n {'Save'}\n </Button>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={Boolean(current.enabled)}\n onChange={(event) => update({ enabled: event.target.checked })}\n />\n }\n label=\"Deliver orders yourself\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose local delivery at the cart, enter their postal code and book a delivery time. ' +\n 'You mark each order out for delivery and delivered, and the buyer is told at each step.'}\n </Typography>\n {problems.length ? (\n <Alert severity=\"warning\">{`Checkout won’t offer delivery yet: ${problems.join(' ')}`}</Alert>\n ) : null}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Country\"\n value={current.country ?? ''}\n onChange={(event) => update({ country: event.target.value.toUpperCase().slice(0, 2) })}\n size=\"small\"\n placeholder=\"US\"\n helperText=\"Two letters\"\n sx={{ width: { sm: 120 } }}\n />\n <TextField\n select\n label=\"Leaves from\"\n value={current.locationId ?? ''}\n onChange={(event) => update({ locationId: event.target.value || undefined })}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText=\"Its stock is reserved and sold for delivery orders\"\n >\n <MenuItem value=\"\">{'No particular location'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Zones'}</Typography>\n {zones.map((zone, index) => (\n <Stack key={zone.id} spacing={1}>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Zone name\"\n value={zone.name}\n onChange={(event) => updateZone(index, { name: event.target.value })}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {radiusAvailable || zone.kind === 'radius' ? (\n <TextField\n select\n label=\"Matched by\"\n value={zone.kind}\n onChange={(event) =>\n updateZone(index, { kind: event.target.value as CommerceModel.LocalDeliveryZone['kind'] })\n }\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n >\n <MenuItem value=\"postcode\">{'Postal codes'}</MenuItem>\n <MenuItem value=\"radius\">{'Distance'}</MenuItem>\n </TextField>\n ) : null}\n <Button size=\"small\" color=\"error\" onClick={() => updateZone(index, null)}>\n {'Remove'}\n </Button>\n </Stack>\n {zone.kind === 'radius' ? (\n <TextField\n label=\"Within (km)\"\n type=\"number\"\n value={zone.radiusKm ?? ''}\n onChange={(event) => updateZone(index, { radiusKm: Number(event.target.value) || 0 })}\n size=\"small\"\n sx={{ width: { sm: 160 } }}\n />\n ) : (\n <TextField\n label=\"Postal codes\"\n value={(zone.postcodes ?? []).join(', ')}\n onChange={(event) =>\n updateZone(index, {\n postcodes: event.target.value.split(',').map((code) => code.trim()).filter(Boolean),\n })\n }\n size=\"small\"\n placeholder=\"10001, 100*, 10010-10020\"\n helperText=\"Exact codes, a prefix ending in *, or a range\"\n />\n )}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n label=\"Fee ($)\"\n value={dollars(zone.feeCents)}\n onChange={(event) => updateZone(index, { feeCents: cents(event.target.value) })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Minimum order ($)\"\n value={dollars(zone.minimumCents)}\n onChange={(event) => updateZone(index, { minimumCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n <TextField\n label=\"Free over ($)\"\n value={dollars(zone.freeOverCents)}\n onChange={(event) => updateZone(index, { freeOverCents: cents(event.target.value) || undefined })}\n size=\"small\"\n slotProps={{ htmlInput: { inputMode: 'decimal' } }}\n />\n </Stack>\n <Divider flexItem />\n </Stack>\n ))}\n <Button size=\"small\" sx={{ alignSelf: 'flex-start' }} onClick={addZone}>\n {'Add zone'}\n </Button>\n {zones.some((zone) => zone.kind === 'radius') ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {current.origin\n ? `Distances measure from ${current.origin.lat.toFixed(4)}, ${current.origin.lng.toFixed(4)}.`\n : 'Distance zones measure from the location deliveries leave from, once it is placed on a map.'}\n </Typography>\n <Button size=\"small\" disabled={locating} onClick={handleLocate}>\n {current.origin ? 'Place again' : 'Place on map'}\n </Button>\n </Stack>\n ) : null}\n\n <Divider />\n <Typography variant=\"subtitle2\">{'Delivery times'}</Typography>\n <TextField\n label=\"Delivery windows\"\n value={current.windows ?? ''}\n onChange={(event) => update({ windows: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-12:00\\nMo-Fr 13:00-17:00'}\n error={badWindowLines.length > 0}\n helperText={\n badWindowLines.length\n ? `Line ${badWindowLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-12:00.`\n : 'One window per line. Each line is offered on each of its days.'\n }\n />\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>\n <TextField\n select\n label=\"Order at least\"\n value={String(current.leadTimeMinutes ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES)}\n onChange={(event) => update({ leadTimeMinutes: Number(event.target.value) })}\n size=\"small\"\n helperText=\"Before a window starts\"\n sx={{ flex: 1 }}\n >\n {LEAD_TIMES.map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Book up to (days ahead)\"\n type=\"number\"\n value={current.daysAhead ?? CommerceModel.LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD}\n onChange={(event) =>\n update({\n daysAhead: Math.min(\n CommerceModel.LOCAL_DELIVERY_MAX_DAYS_AHEAD,\n Math.max(1, Math.round(Number(event.target.value)) || 1),\n ),\n })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"What buyers should know\"\n value={current.instructions ?? ''}\n onChange={(event) => update({ instructions: event.target.value })}\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"We text when we’re 10 minutes away.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n </Stack>\n </CardDisplay>\n )\n}\nLocalDeliverySettingsCard.displayName = 'LocalDeliverySettingsCard'\n\nexport default LocalDeliverySettingsCard\n"],"names":["Aglyn","invalidOpeningHoursLines","CommerceModel","CardDisplay","useSnackbar","Alert","Button","Divider","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","authorizedFetch","collection","doc","limit","query","setDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useUser","writeGuardedBySeed","pluginDocsHelp","LEAD_TIMES","LocalDeliverySettingsCard","props","draft","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","locationDocs","idField","setDraft","localDelivery","update","patch","radiusAvailable","setRadiusAvailable","live","encodeURIComponent","then","response","ok","json","answer","Boolean","radius","catch","undefined","zones","updateZone","index","next","splice","addZone","id","createResourceUid","name","kind","postcodes","feeCents","locating","setLocating","handleLocate","locationId","variant","persist","method","headers","body","JSON","stringify","action","payload","origin","error","badWindowLines","windows","problems","localDeliveryProblems","normalizeLocalDeliverySettings","handleSave","value","verdict","subject","unreadable","mergeFields","message","dollars","cents","String","raw","trim","Math","max","round","Number","locations","sort","a","b","localeCompare","header","help","anchor","HeaderProps","size","disabled","length","onClick","contentGutterX","contentGutterY","spacing","control","checked","enabled","onChange","event","target","label","color","severity","join","direction","xs","sm","country","toUpperCase","slice","placeholder","helperText","sx","width","select","flex","map","location","$id","zone","type","radiusKm","split","code","filter","slotProps","htmlInput","inputMode","minimumCents","freeOverCents","flexItem","alignSelf","some","alignItems","lat","toFixed","lng","multiline","minRows","leadTimeMinutes","LOCAL_DELIVERY_DEFAULT_LEAD_MINUTES","minutes","daysAhead","LOCAL_DELIVERY_DEFAULT_DAYS_AHEAD","min","LOCAL_DELIVERY_MAX_DAYS_AHEAD","instructions","maxLength","PICKUP_INSTRUCTIONS_MAX","displayName"],"mappings":"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"}
|
|
@@ -258,7 +258,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
|
258
258
|
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
259
259
|
header: 'Inventory locations',
|
|
260
260
|
help: pluginDocsHelp('commerce', {
|
|
261
|
-
anchor: '#inventory'
|
|
261
|
+
anchor: '#inventory-locations'
|
|
262
262
|
}),
|
|
263
263
|
contentGutterX: true,
|
|
264
264
|
contentGutterY: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/locations-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport {\n collection,\n deleteDoc,\n doc,\n getCountFromServer,\n limit,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocationsCardProps {\n hostId: string\n}\n\n/**\n * Inventory locations (AGL-286): named stock buckets under\n * `hosts/{hostId}/locations`, capped by the plan's `inventoryLocations`\n * quota (AGL-278). Variant stock splits per location in the products\n * hub; POS registers sell from their location (AGL-312).\n */\nexport function LocationsCard(props: LocationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n /**\n * The location HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The smallest margin of the eight, and real for exactly one plan: the\n * listener is `limit(25)` and `inventoryLocations` runs 1 / 1 / 2 / 4 / 6 /\n * 10 / 50, so only Agency's 50 sits above the window. An agency with more\n * than 25 stock buckets read its cap as satisfiable, and\n * `api/hosts/resources` — which counts the collection — refused the add.\n *\n * Re-read after an add AND a delete: locations are hard-deleted here, so\n * unlike the soft-deleting cards a removal really does free a slot on both\n * sides, and a one-shot that missed it would keep refusing.\n *\n * `locations.length === 0` below still decides the FIRST location's\n * `isDefault` flag off the loaded rows, which is the right question for it\n * — that one is about what this card can see, not about the site's total.\n */\n const [locationCountEpoch, setLocationCountEpoch] = useState(0)\n const [serverLocationCount, setServerLocationCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'locations'))\n .then((snapshot) => {\n if (active) setServerLocationCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows — a LOWER bound and the prior\n // behaviour, never 0, which reads as \"no locations used\" on a site\n // that is over its band.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, locationCountEpoch])\n const locationCount = serverLocationCount ?? locations.length\n const quota = Aglyn.checkQuota(org, 'inventoryLocations', locationCount)\n const [name, setName] = useState('')\n\n const handleAdd = useCallback(async () => {\n if (!name.trim()) return\n // Held until the org doc lands (AGL-1064): an absent org resolves to\n // the free tier's `inventoryLocations: 1`, so a site that already has\n // its default location would be refused with an upgrade prompt it does\n // not need. The button is disabled too; this guards the race.\n if (!planReady) return\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${quota.limit} locations — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n // Creation rides the quota-enforcing resources API (AGL-473).\n await createHostResource({\n hostId,\n resource: 'location',\n data: {\n name: name.trim().slice(0, 80),\n ...(locations.length === 0 ? { isDefault: true } : {}),\n } satisfies CommerceModel.InventoryLocation,\n })\n setName('')\n setLocationCountEpoch((epoch) => epoch + 1)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not add location', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [\n name,\n planReady,\n quota,\n locations.length,\n hostId,\n createHostResource,\n enqueueSnackbar,\n ])\n\n const handleDelete = useCallback(\n (location: any) => async () => {\n const confirmed = await confirm({\n title: 'Remove this location?',\n description:\n `Stock bucketed under \"${location.name}\" folds back into the ` +\n 'flat totals; adjust counts afterward if needed.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(doc(firestore, 'hosts', hostId, 'locations', location.$id))\n // A HARD delete, so the slot really is freed on both sides — the\n // one-shot aggregate has to be told (AGL-1716).\n setLocationCountEpoch((epoch) => epoch + 1)\n },\n [confirm, firestore, hostId],\n )\n\n const handleMakeDefault = useCallback(\n (location: any) => async () => {\n for (const other of locations) {\n if (other.isDefault && other.$id !== location.$id) {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', other.$id),\n { isDefault: false },\n { merge: true },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', location.$id),\n { isDefault: true },\n { merge: true },\n )\n },\n [locations, firestore, hostId],\n )\n\n /*\n * A location's postal address (AGL-3612): where parcels can leave from,\n * for anything that ships. Optional, written beside the free-text name.\n */\n const [addressFor, setAddressFor] = useState<any | null>(null)\n const [addressDraft, setAddressDraft] = useState<CommerceModel.PostalAddress>({})\n const openAddress = useCallback(\n (location: any) => () => {\n setAddressFor(location)\n setAddressDraft(CommerceModel.normalizePostalAddress(location.postalAddress) ?? {})\n },\n [],\n )\n const handleSaveAddress = useCallback(async () => {\n if (!addressFor) return\n try {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', addressFor.$id),\n { postalAddress: CommerceModel.normalizePostalAddress(addressDraft) ?? {} },\n { merge: true },\n )\n setAddressFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save the address', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [addressDraft, addressFor, enqueueSnackbar, firestore, hostId])\n const addressField = (field: keyof CommerceModel.PostalAddress, label: string) => (\n <TextField\n label={label}\n value={addressDraft[field] ?? ''}\n onChange={(event) =>\n setAddressDraft((prior) => ({\n ...prior,\n [field]: field === 'country' ? event.target.value.toUpperCase().slice(0, 2) : event.target.value,\n }))\n }\n size=\"small\"\n />\n )\n\n /*\n * Pickup at a location (AGL-3624): whether buyers may collect orders here,\n * its hours and what to do on arrival. Checkout offers every location that\n * has it on, and routes the order and its stock there.\n */\n const [pickupFor, setPickupFor] = useState<any | null>(null)\n const [pickupDraft, setPickupDraft] = useState<CommerceModel.PickupLocationSettings>({})\n const openPickup = useCallback(\n (location: any) => () => {\n setPickupFor(location)\n setPickupDraft(CommerceModel.normalizePickupSettings(location.pickup))\n },\n [],\n )\n const badHourLines = invalidOpeningHoursLines(pickupDraft.hours ?? '')\n const handleSavePickup = useCallback(async () => {\n if (!pickupFor) return\n try {\n // `updateDoc` replaces the map whole, so a cleared field is cleared;\n // a merge-set would deep-merge it back.\n await updateDoc(doc(firestore, 'hosts', hostId, 'locations', pickupFor.$id), {\n pickup: CommerceModel.normalizePickupSettings(pickupDraft),\n })\n setPickupFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save pickup', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [enqueueSnackbar, firestore, hostId, pickupDraft, pickupFor])\n\n return (\n <CardDisplay\n header={'Inventory locations'}\n help={pluginDocsHelp('commerce', { anchor: '#inventory' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {locations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Track stock per warehouse or storefront. Without locations, ' +\n 'every variant has a single stock count.'}\n </Typography>\n ) : (\n locations.map((location: any) => (\n <Stack\n key={location.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {location.name}\n </Typography>\n {CommerceModel.normalizePickupSettings(location.pickup).enabled ? (\n <Chip label=\"Pickup\" size=\"small\" color=\"primary\" variant=\"outlined\" />\n ) : null}\n <Button size=\"small\" onClick={openPickup(location)}>\n {'Pickup'}\n </Button>\n <Button size=\"small\" onClick={openAddress(location)}>\n {location.postalAddress?.line1 ? 'Edit address' : 'Add address'}\n </Button>\n {location.isDefault ? (\n <Chip label=\"Default\" size=\"small\" variant=\"outlined\" />\n ) : (\n <Button size=\"small\" onClick={handleMakeDefault(location)}>\n {'Make default'}\n </Button>\n )}\n <Button size=\"small\" color=\"error\" onClick={handleDelete(location)}>\n {'Remove'}\n </Button>\n </Stack>\n ))\n )}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"New location\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n placeholder=\"Main warehouse\"\n />\n <Button\n size=\"small\"\n disabled={!name.trim() || !planReady}\n onClick={handleAdd}\n >\n {'Add'}\n </Button>\n </Stack>\n {/* The site's locations, not this card's rows (AGL-1716) — the\n readout and the gate must agree, and the gate now counts.\n The wording moved to the shared component in AGL-2113 so the five\n sibling cards that were missing it render the identical string\n rather than five near-misses; the output here is unchanged. */}\n <QuotaReadoutComponent\n ready={planReady}\n used={locationCount}\n limit={quota.limit}\n noun=\"location\"\n />\n </Stack>\n <Dialog open={Boolean(addressFor)} onClose={() => setAddressFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Address of ${addressFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {addressField('line1', 'Street address')}\n {addressField('line2', 'Apartment, suite (optional)')}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5}>\n {addressField('city', 'City')}\n {addressField('state', 'State or region')}\n {addressField('postalCode', 'Postal code')}\n {addressField('country', 'Country (two letters)')}\n </Stack>\n {addressField('phone', 'Phone')}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAddressFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={handleSaveAddress}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog open={Boolean(pickupFor)} onClose={() => setPickupFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Pickup at ${pickupFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <FormControlLabel\n control={\n <Switch\n checked={Boolean(pickupDraft.enabled)}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, enabled: event.target.checked }))\n }\n />\n }\n label=\"Buyers can pick up orders here\"\n />\n {pickupDraft.enabled && !pickupFor?.postalAddress?.line1 && !pickupFor?.address ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Add this location’s address so buyers know where to go.'}\n </Typography>\n ) : null}\n <TextField\n label=\"Pickup hours\"\n value={pickupDraft.hours ?? ''}\n onChange={(event) => setPickupDraft((prior) => ({ ...prior, hours: event.target.value }))}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-17:00\\nSa 10:00-14:00'}\n error={badHourLines.length > 0}\n helperText={\n badHourLines.length\n ? `Line ${badHourLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-17:00.`\n : 'One line per set of days, like Mo-Fr 09:00-17:00.'\n }\n />\n <TextField\n label=\"Arrival instructions\"\n value={pickupDraft.instructions ?? ''}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, instructions: event.target.value }))\n }\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"Ring the bell at the side door and give your order number.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n <TextField\n select\n label=\"Usually ready in\"\n value={String(pickupDraft.readyWithinMinutes ?? '')}\n onChange={(event) =>\n setPickupDraft((prior) => ({\n ...prior,\n readyWithinMinutes: event.target.value ? Number(event.target.value) : undefined,\n }))\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'Don’t say'}</MenuItem>\n {[\n [30, '30 minutes'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n ].map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose this location at checkout. The order’s stock comes off this location, ' +\n 'and the buyer is emailed when you mark the order ready.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setPickupFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={badHourLines.length > 0} onClick={handleSavePickup}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nLocationsCard.displayName = 'LocationsCard'\n\nexport default LocationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","QuotaReadoutComponent","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","invalidOpeningHoursLines","collection","deleteDoc","doc","getCountFromServer","limit","query","setDoc","updateDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useOrgPlan","pluginDocsHelp","LocationsCard","props","pickupDraft","pickupFor","hostId","firestore","enqueueSnackbar","confirm","createHostResource","org","ready","planReady","data","locationDocs","idField","locations","sort","a","b","String","name","localeCompare","locationCountEpoch","setLocationCountEpoch","serverLocationCount","setServerLocationCount","active","then","snapshot","count","catch","locationCount","length","quota","checkQuota","setName","handleAdd","trim","allowed","variant","persist","resource","slice","isDefault","epoch","error","message","handleDelete","location","confirmed","title","description","confirmationText","confirmationButtonProps","color","$id","handleMakeDefault","other","merge","addressFor","setAddressFor","addressDraft","setAddressDraft","openAddress","normalizePostalAddress","postalAddress","handleSaveAddress","addressField","field","label","value","onChange","event","prior","target","toUpperCase","size","setPickupFor","setPickupDraft","openPickup","normalizePickupSettings","pickup","badHourLines","hours","handleSavePickup","header","help","anchor","contentGutterX","contentGutterY","spacing","map","direction","sx","alignItems","flex","noWrap","enabled","onClick","line1","placeholder","disabled","used","noun","open","Boolean","onClose","fullWidth","maxWidth","pt","xs","sm","control","checked","address","multiline","minRows","helperText","join","instructions","slotProps","htmlInput","maxLength","PICKUP_INSTRUCTIONS_MAX","select","readyWithinMinutes","Number","undefined","minutes","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/locations-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport * as CommerceModel from '../../model'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport QuotaReadoutComponent from '@aglyn/shared-ui-jsx/components/quota-readout.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { invalidOpeningHoursLines } from '@aglyn/aglyn/app-utils/local-business'\nimport {\n collection,\n deleteDoc,\n doc,\n getCountFromServer,\n limit,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { useHostResourceApi } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface LocationsCardProps {\n hostId: string\n}\n\n/**\n * Inventory locations (AGL-286): named stock buckets under\n * `hosts/{hostId}/locations`, capped by the plan's `inventoryLocations`\n * quota (AGL-278). Variant stock splits per location in the products\n * hub; POS registers sell from their location (AGL-312).\n */\nexport function LocationsCard(props: LocationsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const { org, ready: planReady } = useOrgPlan(hostId)\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n /**\n * The location HEAD-COUNT is a server aggregate, not the length of the\n * capped listener (AGL-1716, the AGL-1706 shape).\n *\n * The smallest margin of the eight, and real for exactly one plan: the\n * listener is `limit(25)` and `inventoryLocations` runs 1 / 1 / 2 / 4 / 6 /\n * 10 / 50, so only Agency's 50 sits above the window. An agency with more\n * than 25 stock buckets read its cap as satisfiable, and\n * `api/hosts/resources` — which counts the collection — refused the add.\n *\n * Re-read after an add AND a delete: locations are hard-deleted here, so\n * unlike the soft-deleting cards a removal really does free a slot on both\n * sides, and a one-shot that missed it would keep refusing.\n *\n * `locations.length === 0` below still decides the FIRST location's\n * `isDefault` flag off the loaded rows, which is the right question for it\n * — that one is about what this card can see, not about the site's total.\n */\n const [locationCountEpoch, setLocationCountEpoch] = useState(0)\n const [serverLocationCount, setServerLocationCount] = useState<number | null>(\n null,\n )\n useEffect(() => {\n let active = true\n void getCountFromServer(collection(firestore, 'hosts', hostId, 'locations'))\n .then((snapshot) => {\n if (active) setServerLocationCount(snapshot.data().count)\n })\n .catch(() => {\n // Falls back to the loaded rows — a LOWER bound and the prior\n // behaviour, never 0, which reads as \"no locations used\" on a site\n // that is over its band.\n })\n return () => {\n active = false\n }\n }, [firestore, hostId, locationCountEpoch])\n const locationCount = serverLocationCount ?? locations.length\n const quota = Aglyn.checkQuota(org, 'inventoryLocations', locationCount)\n const [name, setName] = useState('')\n\n const handleAdd = useCallback(async () => {\n if (!name.trim()) return\n // Held until the org doc lands (AGL-1064): an absent org resolves to\n // the free tier's `inventoryLocations: 1`, so a site that already has\n // its default location would be refused with an upgrade prompt it does\n // not need. The button is disabled too; this guards the race.\n if (!planReady) return\n if (!quota.allowed) {\n return void enqueueSnackbar(\n `Your plan includes ${quota.limit} locations — upgrade for more`,\n { variant: 'info', persist: false },\n )\n }\n try {\n // Creation rides the quota-enforcing resources API (AGL-473).\n await createHostResource({\n hostId,\n resource: 'location',\n data: {\n name: name.trim().slice(0, 80),\n ...(locations.length === 0 ? { isDefault: true } : {}),\n } satisfies CommerceModel.InventoryLocation,\n })\n setName('')\n setLocationCountEpoch((epoch) => epoch + 1)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not add location', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [\n name,\n planReady,\n quota,\n locations.length,\n hostId,\n createHostResource,\n enqueueSnackbar,\n ])\n\n const handleDelete = useCallback(\n (location: any) => async () => {\n const confirmed = await confirm({\n title: 'Remove this location?',\n description:\n `Stock bucketed under \"${location.name}\" folds back into the ` +\n 'flat totals; adjust counts afterward if needed.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await deleteDoc(doc(firestore, 'hosts', hostId, 'locations', location.$id))\n // A HARD delete, so the slot really is freed on both sides — the\n // one-shot aggregate has to be told (AGL-1716).\n setLocationCountEpoch((epoch) => epoch + 1)\n },\n [confirm, firestore, hostId],\n )\n\n const handleMakeDefault = useCallback(\n (location: any) => async () => {\n for (const other of locations) {\n if (other.isDefault && other.$id !== location.$id) {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', other.$id),\n { isDefault: false },\n { merge: true },\n )\n }\n }\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', location.$id),\n { isDefault: true },\n { merge: true },\n )\n },\n [locations, firestore, hostId],\n )\n\n /*\n * A location's postal address (AGL-3612): where parcels can leave from,\n * for anything that ships. Optional, written beside the free-text name.\n */\n const [addressFor, setAddressFor] = useState<any | null>(null)\n const [addressDraft, setAddressDraft] = useState<CommerceModel.PostalAddress>({})\n const openAddress = useCallback(\n (location: any) => () => {\n setAddressFor(location)\n setAddressDraft(CommerceModel.normalizePostalAddress(location.postalAddress) ?? {})\n },\n [],\n )\n const handleSaveAddress = useCallback(async () => {\n if (!addressFor) return\n try {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'locations', addressFor.$id),\n { postalAddress: CommerceModel.normalizePostalAddress(addressDraft) ?? {} },\n { merge: true },\n )\n setAddressFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save the address', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [addressDraft, addressFor, enqueueSnackbar, firestore, hostId])\n const addressField = (field: keyof CommerceModel.PostalAddress, label: string) => (\n <TextField\n label={label}\n value={addressDraft[field] ?? ''}\n onChange={(event) =>\n setAddressDraft((prior) => ({\n ...prior,\n [field]: field === 'country' ? event.target.value.toUpperCase().slice(0, 2) : event.target.value,\n }))\n }\n size=\"small\"\n />\n )\n\n /*\n * Pickup at a location (AGL-3624): whether buyers may collect orders here,\n * its hours and what to do on arrival. Checkout offers every location that\n * has it on, and routes the order and its stock there.\n */\n const [pickupFor, setPickupFor] = useState<any | null>(null)\n const [pickupDraft, setPickupDraft] = useState<CommerceModel.PickupLocationSettings>({})\n const openPickup = useCallback(\n (location: any) => () => {\n setPickupFor(location)\n setPickupDraft(CommerceModel.normalizePickupSettings(location.pickup))\n },\n [],\n )\n const badHourLines = invalidOpeningHoursLines(pickupDraft.hours ?? '')\n const handleSavePickup = useCallback(async () => {\n if (!pickupFor) return\n try {\n // `updateDoc` replaces the map whole, so a cleared field is cleared;\n // a merge-set would deep-merge it back.\n await updateDoc(doc(firestore, 'hosts', hostId, 'locations', pickupFor.$id), {\n pickup: CommerceModel.normalizePickupSettings(pickupDraft),\n })\n setPickupFor(null)\n } catch (error: any) {\n enqueueSnackbar(error?.message ?? 'Could not save pickup', {\n variant: 'warning',\n persist: false,\n })\n }\n }, [enqueueSnackbar, firestore, hostId, pickupDraft, pickupFor])\n\n return (\n <CardDisplay\n header={'Inventory locations'}\n help={pluginDocsHelp('commerce', { anchor: '#inventory-locations' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {locations.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Track stock per warehouse or storefront. Without locations, ' +\n 'every variant has a single stock count.'}\n </Typography>\n ) : (\n locations.map((location: any) => (\n <Stack\n key={location.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {location.name}\n </Typography>\n {CommerceModel.normalizePickupSettings(location.pickup).enabled ? (\n <Chip label=\"Pickup\" size=\"small\" color=\"primary\" variant=\"outlined\" />\n ) : null}\n <Button size=\"small\" onClick={openPickup(location)}>\n {'Pickup'}\n </Button>\n <Button size=\"small\" onClick={openAddress(location)}>\n {location.postalAddress?.line1 ? 'Edit address' : 'Add address'}\n </Button>\n {location.isDefault ? (\n <Chip label=\"Default\" size=\"small\" variant=\"outlined\" />\n ) : (\n <Button size=\"small\" onClick={handleMakeDefault(location)}>\n {'Make default'}\n </Button>\n )}\n <Button size=\"small\" color=\"error\" onClick={handleDelete(location)}>\n {'Remove'}\n </Button>\n </Stack>\n ))\n )}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"New location\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n placeholder=\"Main warehouse\"\n />\n <Button\n size=\"small\"\n disabled={!name.trim() || !planReady}\n onClick={handleAdd}\n >\n {'Add'}\n </Button>\n </Stack>\n {/* The site's locations, not this card's rows (AGL-1716) — the\n readout and the gate must agree, and the gate now counts.\n The wording moved to the shared component in AGL-2113 so the five\n sibling cards that were missing it render the identical string\n rather than five near-misses; the output here is unchanged. */}\n <QuotaReadoutComponent\n ready={planReady}\n used={locationCount}\n limit={quota.limit}\n noun=\"location\"\n />\n </Stack>\n <Dialog open={Boolean(addressFor)} onClose={() => setAddressFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Address of ${addressFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {addressField('line1', 'Street address')}\n {addressField('line2', 'Apartment, suite (optional)')}\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5}>\n {addressField('city', 'City')}\n {addressField('state', 'State or region')}\n {addressField('postalCode', 'Postal code')}\n {addressField('country', 'Country (two letters)')}\n </Stack>\n {addressField('phone', 'Phone')}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setAddressFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" onClick={handleSaveAddress}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog open={Boolean(pickupFor)} onClose={() => setPickupFor(null)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{`Pickup at ${pickupFor?.name ?? 'this location'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <FormControlLabel\n control={\n <Switch\n checked={Boolean(pickupDraft.enabled)}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, enabled: event.target.checked }))\n }\n />\n }\n label=\"Buyers can pick up orders here\"\n />\n {pickupDraft.enabled && !pickupFor?.postalAddress?.line1 && !pickupFor?.address ? (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Add this location’s address so buyers know where to go.'}\n </Typography>\n ) : null}\n <TextField\n label=\"Pickup hours\"\n value={pickupDraft.hours ?? ''}\n onChange={(event) => setPickupDraft((prior) => ({ ...prior, hours: event.target.value }))}\n multiline\n minRows={2}\n size=\"small\"\n placeholder={'Mo-Fr 09:00-17:00\\nSa 10:00-14:00'}\n error={badHourLines.length > 0}\n helperText={\n badHourLines.length\n ? `Line ${badHourLines.join(', ')} does not read as days and times, like Mo-Fr 09:00-17:00.`\n : 'One line per set of days, like Mo-Fr 09:00-17:00.'\n }\n />\n <TextField\n label=\"Arrival instructions\"\n value={pickupDraft.instructions ?? ''}\n onChange={(event) =>\n setPickupDraft((prior) => ({ ...prior, instructions: event.target.value }))\n }\n multiline\n minRows={2}\n size=\"small\"\n placeholder=\"Ring the bell at the side door and give your order number.\"\n slotProps={{ htmlInput: { maxLength: CommerceModel.PICKUP_INSTRUCTIONS_MAX } }}\n />\n <TextField\n select\n label=\"Usually ready in\"\n value={String(pickupDraft.readyWithinMinutes ?? '')}\n onChange={(event) =>\n setPickupDraft((prior) => ({\n ...prior,\n readyWithinMinutes: event.target.value ? Number(event.target.value) : undefined,\n }))\n }\n size=\"small\"\n >\n <MenuItem value=\"\">{'Don’t say'}</MenuItem>\n {[\n [30, '30 minutes'],\n [60, '1 hour'],\n [120, '2 hours'],\n [240, '4 hours'],\n [1440, '1 day'],\n [2880, '2 days'],\n ].map(([minutes, label]) => (\n <MenuItem key={minutes} value={String(minutes)}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Buyers choose this location at checkout. The order’s stock comes off this location, ' +\n 'and the buyer is emailed when you mark the order ready.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setPickupFor(null)}>{'Cancel'}</Button>\n <Button variant=\"contained\" disabled={badHourLines.length > 0} onClick={handleSavePickup}>\n {'Save'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nLocationsCard.displayName = 'LocationsCard'\n\nexport default LocationsCard\n"],"names":["Aglyn","CommerceModel","CardDisplay","useConfirmationContext","QuotaReadoutComponent","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","invalidOpeningHoursLines","collection","deleteDoc","doc","getCountFromServer","limit","query","setDoc","updateDoc","useCallback","useEffect","useState","useFirestore","useFirestoreCollection","useHostResourceApi","useOrgPlan","pluginDocsHelp","LocationsCard","props","pickupDraft","pickupFor","hostId","firestore","enqueueSnackbar","confirm","createHostResource","org","ready","planReady","data","locationDocs","idField","locations","sort","a","b","String","name","localeCompare","locationCountEpoch","setLocationCountEpoch","serverLocationCount","setServerLocationCount","active","then","snapshot","count","catch","locationCount","length","quota","checkQuota","setName","handleAdd","trim","allowed","variant","persist","resource","slice","isDefault","epoch","error","message","handleDelete","location","confirmed","title","description","confirmationText","confirmationButtonProps","color","$id","handleMakeDefault","other","merge","addressFor","setAddressFor","addressDraft","setAddressDraft","openAddress","normalizePostalAddress","postalAddress","handleSaveAddress","addressField","field","label","value","onChange","event","prior","target","toUpperCase","size","setPickupFor","setPickupDraft","openPickup","normalizePickupSettings","pickup","badHourLines","hours","handleSavePickup","header","help","anchor","contentGutterX","contentGutterY","spacing","map","direction","sx","alignItems","flex","noWrap","enabled","onClick","line1","placeholder","disabled","used","noun","open","Boolean","onClose","fullWidth","maxWidth","pt","xs","sm","control","checked","address","multiline","minRows","helperText","join","instructions","slotProps","htmlInput","maxLength","PICKUP_INSTRUCTIONS_MAX","select","readyWithinMinutes","Number","undefined","minutes","displayName"],"mappings":"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"}
|
|
@@ -39,8 +39,8 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
|
39
39
|
* unentitled operator could mark a post members-only, see it accepted, and
|
|
40
40
|
* have the renderer serve it to the public — believing content was protected
|
|
41
41
|
* while it was not. A refusal the operator can see is the fix.
|
|
42
|
-
*/ const memberPostsHelp = pluginDocsHelp('
|
|
43
|
-
anchor: '#
|
|
42
|
+
*/ const memberPostsHelp = pluginDocsHelp('commerceEndToEnd', {
|
|
43
|
+
anchor: '#member-updates'
|
|
44
44
|
});
|
|
45
45
|
export function MemberPostsCard(props) {
|
|
46
46
|
var _ref, _ref1, _ref2, _ref3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/member-posts-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Checkbox,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n limit,\n orderBy,\n query,\n} from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { EntitlementGatedCard } from './entitlement-gate.component'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface MemberPostsCardProps {\n hostId: string\n}\n\n/**\n * Member updates (AGL-316): posts for entitled subscribers, published\n * through a manager-gated API that can also email the audience. The\n * member-feed block renders them on the site.\n *\n * Gated on `contentGating` since AGL-2080, and this is the one of the five\n * that failed OPEN. `server/gate.ts:48` (`hostHasContentGating`) is what\n * decides whether `membership-content.ts`, `member-feed.ts` and `stream.ts`\n * withhold anything; without the entitlement it withholds nothing. So an\n * unentitled operator could mark a post members-only, see it accepted, and\n * have the renderer serve it to the public — believing content was protected\n * while it was not. A refusal the operator can see is the fix.\n */\n\nconst memberPostsHelp = pluginDocsHelp('membersOnly', {\n anchor: '#manage-your-members',\n})\n\nexport function MemberPostsCard(props: MemberPostsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n /*\n * Ordered by the SERVER, and a growing window rather than a fixed read.\n *\n * This was `limit(50)` with no `orderBy`, rendering `.slice(0, 8)`. A\n * Firestore query with a limit and no ordering returns DOCUMENT-ID order,\n * and posts are created with `.add()`, so those fifty were a pseudo-random\n * sample of the feed — the client sort below then ordered the sample and\n * showed eight of it, which looks newest-first and is not. Past fifty\n * posts, one written this morning could simply never appear.\n */\n const {\n rows: posts,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'memberPosts'),\n orderBy('createdAtMs', 'desc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { data: productDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'products'), limit(200)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<{\n title: string\n body: string\n productId: string\n emailSubscribers: boolean\n busy?: boolean\n } | null>(null)\n\n const handlePublish = useCallback(async () => {\n if (!draft?.title.trim()) return\n setDraft((prev) => (prev ? { ...prev, busy: true } : prev))\n try {\n const response = await authorizedFetch(\n user,\n '/api/commerce/member-post',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, ...draft }),\n },\n )\n const payload = await response.json()\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Publish failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(\n payload.emailed\n ? `Published — emailed ${payload.emailed} subscribers`\n : 'Published',\n { variant: 'success', persist: false },\n )\n setDraft(null)\n } finally {\n setDraft((prev) => (prev ? { ...prev, busy: false } : prev))\n }\n }, [draft, user, hostId, enqueueSnackbar])\n\n return (\n <EntitlementGatedCard\n help={memberPostsHelp}\n hostId={hostId}\n feature=\"contentGating\"\n header={'Member updates'}\n upsell={\n 'Memberships and gated content keep posts, pages and video behind ' +\n 'a subscription, and publish updates to the members entitled to ' +\n 'them.'\n }\n >\n <CardDisplay\n header={'Member updates'}\n help={memberPostsHelp}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {posts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Post updates only your subscribers can see (and optionally ' +\n 'email them). Add a Member feed block to a members-only page.'}\n </Typography>\n ) : (\n posts.map((post: any) => (\n <Stack\n key={post.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {post.title}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {new Date(post.createdAtMs ?? 0).toLocaleDateString()}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n deleteDoc(\n doc(firestore, 'hosts', hostId, 'memberPosts', post.$id),\n )\n }\n >\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={posts.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n setDraft({\n title: '',\n body: '',\n productId: '',\n emailSubscribers: true,\n })\n }\n >\n {'New update'}\n </Button>\n </Stack>\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{'New member update'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Title\"\n value={draft?.title ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, title: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n label=\"Body\"\n value={draft?.body ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, body: event.target.value } : prev,\n )\n }\n size=\"small\"\n multiline\n minRows={4}\n />\n <TextField\n label=\"Audience\"\n value={draft?.productId ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, productId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n >\n <MenuItem value=\"\">{'All live subscribers'}</MenuItem>\n {(productDocs ?? [])\n .filter((product: any) => product.subscription)\n .map((product: any) => (\n <MenuItem key={product.$id} value={product.$id}>\n {`Subscribers of ${product.name}`}\n </MenuItem>\n ))}\n </TextField>\n <FormControlLabel\n control={\n <Checkbox\n size=\"small\"\n checked={draft?.emailSubscribers ?? true}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? { ...prev, emailSubscribers: event.target.checked }\n : prev,\n )\n }\n />\n }\n label=\"Email subscribers\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.title.trim() || draft?.busy}\n onClick={handlePublish}\n >\n {'Publish'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n </EntitlementGatedCard>\n )\n}\nMemberPostsCard.displayName = 'MemberPostsCard'\n\nexport default MemberPostsCard\n"],"names":["CardDisplay","ListPagination","useSnackbar","Button","Checkbox","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","limit","orderBy","query","useCallback","useState","useFirestore","useUser","useFirestoreCollection","usePagedCollection","EntitlementGatedCard","pluginDocsHelp","authorizedFetch","memberPostsHelp","anchor","MemberPostsCard","props","hostId","firestore","data","user","enqueueSnackbar","rows","posts","hasMore","page","setPage","pageSize","setPageSize","pageLimit","idField","productDocs","draft","setDraft","handlePublish","title","trim","prev","busy","response","method","headers","body","JSON","stringify","payload","json","ok","error","variant","allowDuplicate","emailed","persist","help","feature","header","upsell","contentGutterX","contentGutterY","spacing","length","color","map","post","direction","sx","alignItems","flex","minWidth","noWrap","Date","createdAtMs","toLocaleDateString","size","onClick","$id","rowCount","onPageChange","onPageSizeChange","alignSelf","productId","emailSubscribers","open","Boolean","onClose","maxWidth","fullWidth","display","flexDirection","gap","label","value","onChange","event","target","autoFocus","mt","multiline","minRows","select","filter","product","subscription","name","control","checked","disabled","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/member-posts-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Checkbox,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteDoc,\n doc,\n limit,\n orderBy,\n query,\n} from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreCollection,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { EntitlementGatedCard } from './entitlement-gate.component'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface MemberPostsCardProps {\n hostId: string\n}\n\n/**\n * Member updates (AGL-316): posts for entitled subscribers, published\n * through a manager-gated API that can also email the audience. The\n * member-feed block renders them on the site.\n *\n * Gated on `contentGating` since AGL-2080, and this is the one of the five\n * that failed OPEN. `server/gate.ts:48` (`hostHasContentGating`) is what\n * decides whether `membership-content.ts`, `member-feed.ts` and `stream.ts`\n * withhold anything; without the entitlement it withholds nothing. So an\n * unentitled operator could mark a post members-only, see it accepted, and\n * have the renderer serve it to the public — believing content was protected\n * while it was not. A refusal the operator can see is the fix.\n */\n\nconst memberPostsHelp = pluginDocsHelp('commerceEndToEnd', {\n anchor: '#member-updates',\n})\n\nexport function MemberPostsCard(props: MemberPostsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n /*\n * Ordered by the SERVER, and a growing window rather than a fixed read.\n *\n * This was `limit(50)` with no `orderBy`, rendering `.slice(0, 8)`. A\n * Firestore query with a limit and no ordering returns DOCUMENT-ID order,\n * and posts are created with `.add()`, so those fifty were a pseudo-random\n * sample of the feed — the client sort below then ordered the sample and\n * showed eight of it, which looks newest-first and is not. Past fifty\n * posts, one written this morning could simply never appear.\n */\n const {\n rows: posts,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<any>(\n (pageLimit) =>\n query(\n collection(firestore, 'hosts', hostId, 'memberPosts'),\n orderBy('createdAtMs', 'desc'),\n limit(pageLimit),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { data: productDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'products'), limit(200)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const [draft, setDraft] = useState<{\n title: string\n body: string\n productId: string\n emailSubscribers: boolean\n busy?: boolean\n } | null>(null)\n\n const handlePublish = useCallback(async () => {\n if (!draft?.title.trim()) return\n setDraft((prev) => (prev ? { ...prev, busy: true } : prev))\n try {\n const response = await authorizedFetch(\n user,\n '/api/commerce/member-post',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, ...draft }),\n },\n )\n const payload = await response.json()\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Publish failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(\n payload.emailed\n ? `Published — emailed ${payload.emailed} subscribers`\n : 'Published',\n { variant: 'success', persist: false },\n )\n setDraft(null)\n } finally {\n setDraft((prev) => (prev ? { ...prev, busy: false } : prev))\n }\n }, [draft, user, hostId, enqueueSnackbar])\n\n return (\n <EntitlementGatedCard\n help={memberPostsHelp}\n hostId={hostId}\n feature=\"contentGating\"\n header={'Member updates'}\n upsell={\n 'Memberships and gated content keep posts, pages and video behind ' +\n 'a subscription, and publish updates to the members entitled to ' +\n 'them.'\n }\n >\n <CardDisplay\n header={'Member updates'}\n help={memberPostsHelp}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {posts.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Post updates only your subscribers can see (and optionally ' +\n 'email them). Add a Member feed block to a members-only page.'}\n </Typography>\n ) : (\n posts.map((post: any) => (\n <Stack\n key={post.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {post.title}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {new Date(post.createdAtMs ?? 0).toLocaleDateString()}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n deleteDoc(\n doc(firestore, 'hosts', hostId, 'memberPosts', post.$id),\n )\n }\n >\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={posts.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n setDraft({\n title: '',\n body: '',\n productId: '',\n emailSubscribers: true,\n })\n }\n >\n {'New update'}\n </Button>\n </Stack>\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{'New member update'}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <TextField\n label=\"Title\"\n value={draft?.title ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, title: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n label=\"Body\"\n value={draft?.body ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, body: event.target.value } : prev,\n )\n }\n size=\"small\"\n multiline\n minRows={4}\n />\n <TextField\n label=\"Audience\"\n value={draft?.productId ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, productId: event.target.value } : prev,\n )\n }\n size=\"small\"\n select\n >\n <MenuItem value=\"\">{'All live subscribers'}</MenuItem>\n {(productDocs ?? [])\n .filter((product: any) => product.subscription)\n .map((product: any) => (\n <MenuItem key={product.$id} value={product.$id}>\n {`Subscribers of ${product.name}`}\n </MenuItem>\n ))}\n </TextField>\n <FormControlLabel\n control={\n <Checkbox\n size=\"small\"\n checked={draft?.emailSubscribers ?? true}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? { ...prev, emailSubscribers: event.target.checked }\n : prev,\n )\n }\n />\n }\n label=\"Email subscribers\"\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.title.trim() || draft?.busy}\n onClick={handlePublish}\n >\n {'Publish'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n </EntitlementGatedCard>\n )\n}\nMemberPostsCard.displayName = 'MemberPostsCard'\n\nexport default MemberPostsCard\n"],"names":["CardDisplay","ListPagination","useSnackbar","Button","Checkbox","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","MenuItem","Stack","TextField","Typography","collection","deleteDoc","doc","limit","orderBy","query","useCallback","useState","useFirestore","useUser","useFirestoreCollection","usePagedCollection","EntitlementGatedCard","pluginDocsHelp","authorizedFetch","memberPostsHelp","anchor","MemberPostsCard","props","hostId","firestore","data","user","enqueueSnackbar","rows","posts","hasMore","page","setPage","pageSize","setPageSize","pageLimit","idField","productDocs","draft","setDraft","handlePublish","title","trim","prev","busy","response","method","headers","body","JSON","stringify","payload","json","ok","error","variant","allowDuplicate","emailed","persist","help","feature","header","upsell","contentGutterX","contentGutterY","spacing","length","color","map","post","direction","sx","alignItems","flex","minWidth","noWrap","Date","createdAtMs","toLocaleDateString","size","onClick","$id","rowCount","onPageChange","onPageSizeChange","alignSelf","productId","emailSubscribers","open","Boolean","onClose","maxWidth","fullWidth","display","flexDirection","gap","label","value","onChange","event","target","autoFocus","mt","multiline","minRows","select","filter","product","subscription","name","control","checked","disabled","displayName"],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ import { describePaymentRisk } from "@aglyn/aglyn/app-utils/payment-risk";
|
|
|
29
29
|
import { AppLink, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
30
30
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
31
31
|
import { Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, Stack, TextField, Tooltip, Typography } from "@mui/material";
|
|
32
|
-
import { doc, runTransaction, updateDoc } from "firebase/firestore";
|
|
32
|
+
import { doc, getDoc, runTransaction, updateDoc } from "firebase/firestore";
|
|
33
33
|
import { useParams } from "next/navigation";
|
|
34
34
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
35
35
|
import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
|
|
@@ -88,13 +88,33 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
88
88
|
* write), notes, printable packing slip.
|
|
89
89
|
*/ export function OrderDetailDialog(props) {
|
|
90
90
|
var _STATUS_COLOR_order_status, _order_lineItems, _order_extras, _order_timeline;
|
|
91
|
-
var _restock_lines;
|
|
92
91
|
const { hostId, order: rawOrder, onClose } = props;
|
|
93
92
|
const firestore = useFirestore();
|
|
94
93
|
const { data: user } = useUser();
|
|
95
94
|
const { enqueueSnackbar } = useSnackbar();
|
|
96
95
|
const { confirm } = useConfirmationContext();
|
|
97
96
|
const [fulfilling, setFulfilling] = useState(false);
|
|
97
|
+
// The store's currency, which a hand-entered shipping cost is typed in
|
|
98
|
+
// (AGL-3705). Read when the Fulfill items panel opens, not before.
|
|
99
|
+
const [storeCurrency, setStoreCurrency] = useState(undefined);
|
|
100
|
+
useEffect(()=>{
|
|
101
|
+
if (!fulfilling || storeCurrency) return;
|
|
102
|
+
let live = true;
|
|
103
|
+
// A failed read leaves the field in US dollars, the store default.
|
|
104
|
+
Promise.resolve().then(()=>getDoc(doc(firestore, 'hosts', hostId, 'settings', 'store'))).then((snapshot)=>{
|
|
105
|
+
var _snapshot_get;
|
|
106
|
+
const currency = String((_snapshot_get = snapshot.get('currency')) != null ? _snapshot_get : '').trim();
|
|
107
|
+
if (live && currency) setStoreCurrency(currency.toUpperCase());
|
|
108
|
+
}).catch(()=>undefined);
|
|
109
|
+
return ()=>{
|
|
110
|
+
live = false;
|
|
111
|
+
};
|
|
112
|
+
}, [
|
|
113
|
+
fulfilling,
|
|
114
|
+
storeCurrency,
|
|
115
|
+
firestore,
|
|
116
|
+
hostId
|
|
117
|
+
]);
|
|
98
118
|
const WidgetSlot = useConsoleWidgetSlot();
|
|
99
119
|
const [note, setNote] = useState('');
|
|
100
120
|
const [busy, setBusy] = useState(false);
|
|
@@ -247,6 +267,8 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
247
267
|
trackingUrl: submission.trackingUrl
|
|
248
268
|
} : {}, submission.labelUrl ? {
|
|
249
269
|
labelUrl: submission.labelUrl
|
|
270
|
+
} : {}, typeof submission.shippingCostCents === 'number' ? {
|
|
271
|
+
shippingCostCents: submission.shippingCostCents
|
|
250
272
|
} : {}, submission.notify ? {} : {
|
|
251
273
|
notify: false
|
|
252
274
|
}), {
|
|
@@ -600,22 +622,12 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
600
622
|
const snapshot = await transaction.get(reference);
|
|
601
623
|
if (!snapshot.exists()) return 'gone';
|
|
602
624
|
const current = CommerceModel.liftLegacyOrder((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
|
|
603
|
-
const
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
transaction.update(reference,
|
|
609
|
-
restockCheck: _extends({}, fresh, {
|
|
610
|
-
resolution,
|
|
611
|
-
resolvedAtMs
|
|
612
|
-
}, uid ? {
|
|
613
|
-
resolvedBy: uid
|
|
614
|
-
} : {}),
|
|
615
|
-
// The SERVER's timeline, not the dialog's — a note landed from
|
|
616
|
-
// another tab must survive the append.
|
|
617
|
-
timeline: CommerceModel.appendOrderEvent(current, 'restock-check', resolution === 'restocked' ? 'answered — restocked' : 'answered — no restock', resolvedAtMs)
|
|
618
|
-
});
|
|
625
|
+
const answer = CommerceModel.restockAnswer(current, check.flaggedAtMs, resolution, String((_ref = user == null ? void 0 : user.uid) != null ? _ref : ''));
|
|
626
|
+
if (answer.verdict !== 'recorded') return answer.verdict;
|
|
627
|
+
// The SERVER's timeline, not the dialog's — a note landed from
|
|
628
|
+
// another tab must survive the append (`restockAnswer` appends to
|
|
629
|
+
// `current`).
|
|
630
|
+
transaction.update(reference, answer.update);
|
|
619
631
|
return 'recorded';
|
|
620
632
|
});
|
|
621
633
|
if (verdict === 'recorded') {
|
|
@@ -660,10 +672,9 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
660
672
|
enqueueSnackbar
|
|
661
673
|
]);
|
|
662
674
|
const handleNote = useCallback(async ()=>{
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
});
|
|
675
|
+
const update = order ? CommerceModel.orderNoteUpdate(order, note) : null;
|
|
676
|
+
if (!update) return;
|
|
677
|
+
await write(update);
|
|
667
678
|
setNote('');
|
|
668
679
|
}, [
|
|
669
680
|
order,
|
|
@@ -727,7 +738,6 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
727
738
|
// prompt written before line indexes existed reads as the amount-only case
|
|
728
739
|
// it was.
|
|
729
740
|
const fulfilledByLine = CommerceModel.orderLineFulfillmentStates(order).map((state)=>state.fulfilledQuantity);
|
|
730
|
-
const restockLinesNamed = Boolean((restock == null ? void 0 : (_restock_lines = restock.lines) == null ? void 0 : _restock_lines.length) && restock.lines.every((line)=>line.lineIndex != null && CommerceModel.orderLineRefunded(order, line.lineIndex)));
|
|
731
741
|
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
732
742
|
open: true,
|
|
733
743
|
onClose: onClose,
|
|
@@ -928,13 +938,7 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
928
938
|
/*#__PURE__*/ _jsx(Typography, {
|
|
929
939
|
variant: "body2",
|
|
930
940
|
color: "text.secondary",
|
|
931
|
-
children:
|
|
932
|
-
// wording: the shopper kept the item and took the money.
|
|
933
|
-
(restock.kind === 'chargeback' ? ' The shopper kept the goods unless they actually came back.' : '') + // On a partial the flagged units are the MOST it could be —
|
|
934
|
-
// UNLESS the refund named its lines (AGL-2325), in which
|
|
935
|
-
// case the question is scoped to exactly those and the
|
|
936
|
-
// merchant is only being asked whether the goods came back.
|
|
937
|
-
(restock.fullyReversed ? '' : restockLinesNamed ? ' Only part of the money came back: these are the lines withdrawn by this refund, so the units are theirs — only you know whether the goods came back.' : ' Only part of the money came back, so these units are an upper bound — only you know which goods returned.')
|
|
941
|
+
children: CommerceModel.describeRestockCheck(restock, order)
|
|
938
942
|
}),
|
|
939
943
|
restock.lines.map((line, index)=>{
|
|
940
944
|
var _line_name;
|
|
@@ -1021,6 +1025,7 @@ const usd = (cents)=>`$${((cents != null ? cents : 0) / 100).toFixed(2)}`;
|
|
|
1021
1025
|
order: order,
|
|
1022
1026
|
busy: busy,
|
|
1023
1027
|
onSubmit: handleFulfill,
|
|
1028
|
+
currency: storeCurrency,
|
|
1024
1029
|
onCancel: ()=>setFulfilling(false),
|
|
1025
1030
|
renderZone: WidgetSlot && zoneOrder ? (selection, applyTracking)=>/*#__PURE__*/ _jsx(HostedZone, {
|
|
1026
1031
|
renderer: WidgetSlot,
|