@aglyn/plugins-commerce 1.0.0-beta.277 → 1.0.0-beta.282
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 +17 -14
- package/src/lib/components/commerce-console-page.js +33 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
- package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.js +5 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
- package/src/lib/components/console/pos-hardware-card.component.js +456 -0
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +6 -3
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-zones.d.ts +37 -3
- package/src/lib/components/console/pos-zones.js +11 -1
- package/src/lib/components/console/pos-zones.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
- package/src/lib/components/console/product-check-in-fields.component.js +172 -0
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +48 -9
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
- package/src/lib/components/console/product-register-fields.component.js +39 -1
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +21 -6
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +239 -70
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/reservation-widget.js +16 -0
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/constants/operator-alerts.d.ts +6 -0
- package/src/lib/constants/operator-alerts.js +20 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +7 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +34 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/model/commerce-check-in-product.d.ts +37 -0
- package/src/lib/model/commerce-check-in-product.js +49 -0
- package/src/lib/model/commerce-check-in-product.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +187 -0
- package/src/lib/model/commerce-check-in.js +468 -0
- package/src/lib/model/commerce-check-in.js.map +1 -0
- package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
- package/src/lib/model/commerce-pos-day-report.js +572 -0
- package/src/lib/model/commerce-pos-day-report.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +65 -3
- package/src/lib/model/commerce-pos-offline.js +104 -7
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.d.ts +3 -0
- package/src/lib/model/commerce-pos-ops.js +1 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +43 -3
- package/src/lib/model/commerce-pos.js +73 -5
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +73 -7
- package/src/lib/model/commerce-reservations.js +90 -10
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +40 -0
- package/src/lib/model/commerce.js +22 -4
- 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/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +2 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-hardware.d.ts +288 -0
- package/src/lib/model/pos-hardware.js +499 -0
- package/src/lib/model/pos-hardware.js.map +1 -0
- package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
- package/src/lib/model/pos-offline-card-limits.js +28 -0
- package/src/lib/model/pos-offline-card-limits.js.map +1 -0
- package/src/lib/model/pos-reader-splash.d.ts +114 -0
- package/src/lib/model/pos-reader-splash.js +141 -0
- package/src/lib/model/pos-reader-splash.js.map +1 -0
- package/src/lib/model/product-write.d.ts +6 -0
- package/src/lib/plugin-config.d.ts +10 -0
- package/src/lib/plugin-config.js +40 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +12 -1
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.js +49 -12
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +1 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +7 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +8 -1
- package/src/lib/server/person-eraser.js +23 -1
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-check-in.d.ts +49 -0
- package/src/lib/server/pos-check-in.js +652 -0
- package/src/lib/server/pos-check-in.js.map +1 -0
- package/src/lib/server/pos-day-report.d.ts +46 -0
- package/src/lib/server/pos-day-report.js +299 -0
- package/src/lib/server/pos-day-report.js.map +1 -0
- package/src/lib/server/pos-hardware-admin.d.ts +50 -0
- package/src/lib/server/pos-hardware-admin.js +366 -0
- package/src/lib/server/pos-hardware-admin.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +27 -0
- package/src/lib/server/pos-hardware-notify.js +142 -0
- package/src/lib/server/pos-hardware-notify.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
- package/src/lib/server/pos-hardware-user-eraser.js +67 -0
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
- package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
- package/src/lib/server/pos-hardware-webhook.js +176 -0
- package/src/lib/server/pos-hardware-webhook.js.map +1 -0
- package/src/lib/server/pos-hardware.d.ts +58 -0
- package/src/lib/server/pos-hardware.js +234 -0
- package/src/lib/server/pos-hardware.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
- package/src/lib/server/pos-offline-card-reconcile.js +58 -0
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +32 -0
- package/src/lib/server/pos-offline-sync.js +191 -15
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-routes.d.ts +4 -2
- package/src/lib/server/pos-ops-routes.js +14 -2
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order.js +39 -51
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +8 -4
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
- package/src/lib/server/pos-reader-splash-admin.js +295 -0
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
- package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
- package/src/lib/server/pos-reader-splash-render.js +138 -0
- package/src/lib/server/pos-reader-splash-render.js.map +1 -0
- package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
- package/src/lib/server/pos-reader-splash-server.js +32 -0
- package/src/lib/server/pos-reader-splash-server.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +105 -0
- package/src/lib/server/pos-reader-splash.js +548 -0
- package/src/lib/server/pos-reader-splash.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +2 -1
- package/src/lib/server/pos-sale.js +3 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +67 -4
- package/src/lib/server/pos-terminal.js +239 -32
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/process-restock.js +4 -0
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-card.js +2 -2
- package/src/lib/server/product-card.js.map +1 -1
- package/src/lib/server/product-catalog.d.ts +2 -0
- package/src/lib/server/product-catalog.js +11 -4
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product.js +3 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/related.js +8 -1
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +35 -0
- package/src/lib/server/reservation-availability-query.js +34 -0
- package/src/lib/server/reservation-availability-query.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +21 -0
- package/src/lib/server/reservation-availability.js +64 -10
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-walk-in.d.ts +28 -0
- package/src/lib/server/reservation-walk-in.js +166 -0
- package/src/lib/server/reservation-walk-in.js.map +1 -0
- package/src/lib/server/reserve.js +13 -10
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/reviews.js +3 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +5 -2
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +72 -0
- package/src/lib/testing/fake-firestore.js.map +1 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { useMediaPicker } from "@aglyn/aglyn/app-utils/media-picker-context";
|
|
19
|
+
import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
20
|
+
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
21
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
|
+
import { useConsoleHostRoute, useUser } from "@aglyn/tenant-feature-instance";
|
|
23
|
+
import { Alert, Box, Button, FormControlLabel, Radio, RadioGroup, Stack, Typography } from "@mui/material";
|
|
24
|
+
import { useCallback, useEffect, useState } from "react";
|
|
25
|
+
import { POS_READER_SPLASH_LARGEST, posReaderSplashArtworkProblem } from "../../model/pos-reader-splash.js";
|
|
26
|
+
import { PosReaderSplashRequestError, readerSplashStatus, saveReaderSplash } from "./pos/pos-reader-splash-api.js";
|
|
27
|
+
/** An image's pixel size, read in the browser before anything is saved. */ function imageSize(src) {
|
|
28
|
+
return new Promise((resolve)=>{
|
|
29
|
+
const image = new Image();
|
|
30
|
+
image.onload = ()=>resolve({
|
|
31
|
+
width: image.naturalWidth,
|
|
32
|
+
height: image.naturalHeight
|
|
33
|
+
});
|
|
34
|
+
image.onerror = ()=>resolve(null);
|
|
35
|
+
image.src = src;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const NOT_ALLOWED = 'Only an admin or editor with the "Use the point of sale" permission can change the readers.';
|
|
39
|
+
/**
|
|
40
|
+
* "Reader start-up screen" on the POS devices card (AGL-4048): what the
|
|
41
|
+
* store's Stripe smart readers show while they wait for a payment. The
|
|
42
|
+
* platform's default screen, the workspace's logo on a white screen, or a
|
|
43
|
+
* full-screen portrait image from the media library. A white-label
|
|
44
|
+
* workspace is never offered the platform's screen and shows its own logo
|
|
45
|
+
* unless it picks an image.
|
|
46
|
+
*
|
|
47
|
+
* The server applies the choice to the store's readers when it is saved
|
|
48
|
+
* (and again when this opens and finds the logo changed since); readers
|
|
49
|
+
* pick it up within about ten minutes.
|
|
50
|
+
*/ export function PosReaderSplashSetting({ hostId, canManagePos }) {
|
|
51
|
+
var _ref, _ref1, _ref2;
|
|
52
|
+
var _status_image;
|
|
53
|
+
const { data: user } = useUser();
|
|
54
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
55
|
+
const { pickMedia } = useMediaPicker();
|
|
56
|
+
const { orgSlug } = useConsoleHostRoute(hostId);
|
|
57
|
+
const [status, setStatus] = useState(null);
|
|
58
|
+
const [busy, setBusy] = useState(false);
|
|
59
|
+
const [picked, setPicked] = useState(null);
|
|
60
|
+
const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
|
|
61
|
+
useEffect(()=>{
|
|
62
|
+
if (!user) return;
|
|
63
|
+
let active = true;
|
|
64
|
+
readerSplashStatus(user, hostId).then((answer)=>{
|
|
65
|
+
if (active) setStatus(answer);
|
|
66
|
+
}).catch(()=>{
|
|
67
|
+
if (active) setStatus(null);
|
|
68
|
+
});
|
|
69
|
+
return ()=>{
|
|
70
|
+
active = false;
|
|
71
|
+
};
|
|
72
|
+
// Keyed on the person, not the session object (see the register page).
|
|
73
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
74
|
+
}, [
|
|
75
|
+
uid,
|
|
76
|
+
hostId
|
|
77
|
+
]);
|
|
78
|
+
const save = useCallback(async (choice, image)=>{
|
|
79
|
+
if (!user) return;
|
|
80
|
+
setBusy(true);
|
|
81
|
+
try {
|
|
82
|
+
const answer = await saveReaderSplash(user, hostId, choice, image);
|
|
83
|
+
setStatus(answer);
|
|
84
|
+
if (!answer.problem) {
|
|
85
|
+
enqueueSnackbar(answer.locationReady ? 'Saved. Your readers show it within about ten minutes.' : 'Saved. Your readers show it once you add one.', {
|
|
86
|
+
variant: 'success',
|
|
87
|
+
persist: false
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
enqueueSnackbar(error instanceof PosReaderSplashRequestError ? error.message : 'The start-up screen could not be saved', {
|
|
92
|
+
variant: 'warning',
|
|
93
|
+
persist: false
|
|
94
|
+
});
|
|
95
|
+
} finally{
|
|
96
|
+
setBusy(false);
|
|
97
|
+
}
|
|
98
|
+
}, [
|
|
99
|
+
user,
|
|
100
|
+
hostId,
|
|
101
|
+
enqueueSnackbar
|
|
102
|
+
]);
|
|
103
|
+
const chooseImage = async ()=>{
|
|
104
|
+
var _media_contentType, _ref, _ref1;
|
|
105
|
+
const media = await (pickMedia == null ? void 0 : pickMedia({
|
|
106
|
+
kind: 'image'
|
|
107
|
+
}));
|
|
108
|
+
if (!media) return;
|
|
109
|
+
if (!media.mediaId || !media.mediaScope) {
|
|
110
|
+
enqueueSnackbar('Choose an image from your media library.', {
|
|
111
|
+
variant: 'warning',
|
|
112
|
+
persist: false
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const size = await imageSize(media.url);
|
|
117
|
+
const problem = posReaderSplashArtworkProblem({
|
|
118
|
+
contentType: (_media_contentType = media.contentType) != null ? _media_contentType : null,
|
|
119
|
+
width: (_ref = size == null ? void 0 : size.width) != null ? _ref : 0,
|
|
120
|
+
height: (_ref1 = size == null ? void 0 : size.height) != null ? _ref1 : 0
|
|
121
|
+
});
|
|
122
|
+
if (problem) {
|
|
123
|
+
enqueueSnackbar(problem, {
|
|
124
|
+
variant: 'warning',
|
|
125
|
+
persist: false
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setPicked(media);
|
|
130
|
+
await save('image', {
|
|
131
|
+
mediaScope: media.mediaScope,
|
|
132
|
+
mediaId: media.mediaId
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
if (!(status == null ? void 0 : status.available)) return null;
|
|
136
|
+
const blockedReason = !canManagePos ? NOT_ALLOWED : busy ? 'Saving…' : null;
|
|
137
|
+
const hasLogo = Boolean(status.logoUrl);
|
|
138
|
+
const profileHref = orgSlug ? `/${orgSlug}/settings/profile` : undefined;
|
|
139
|
+
const brand = status.productName;
|
|
140
|
+
const preview = status.effectiveChoice === 'logo' ? status.logoUrl : status.effectiveChoice === 'image' ? (_ref1 = (_ref2 = picked == null ? void 0 : picked.url) != null ? _ref2 : (_status_image = status.image) == null ? void 0 : _status_image.url) != null ? _ref1 : null : null;
|
|
141
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
142
|
+
spacing: 1,
|
|
143
|
+
children: [
|
|
144
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
145
|
+
variant: "subtitle2",
|
|
146
|
+
children: 'Reader start-up screen'
|
|
147
|
+
}),
|
|
148
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
149
|
+
variant: "body2",
|
|
150
|
+
color: "text.secondary",
|
|
151
|
+
children: 'What your card readers show while they wait for a payment.'
|
|
152
|
+
}),
|
|
153
|
+
/*#__PURE__*/ _jsxs(RadioGroup, {
|
|
154
|
+
value: status.effectiveChoice,
|
|
155
|
+
onChange: (event)=>{
|
|
156
|
+
const choice = event.target.value;
|
|
157
|
+
if (choice === 'image') {
|
|
158
|
+
if (status.image) void save('image', {
|
|
159
|
+
mediaScope: status.image.mediaScope,
|
|
160
|
+
mediaId: status.image.mediaId
|
|
161
|
+
});
|
|
162
|
+
else void chooseImage();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
void save(choice);
|
|
166
|
+
},
|
|
167
|
+
children: [
|
|
168
|
+
status.whiteLabel ? null : /*#__PURE__*/ _jsx(FormControlLabel, {
|
|
169
|
+
value: "platform",
|
|
170
|
+
disabled: Boolean(blockedReason),
|
|
171
|
+
control: /*#__PURE__*/ _jsx(Radio, {
|
|
172
|
+
size: "small"
|
|
173
|
+
}),
|
|
174
|
+
label: /*#__PURE__*/ _jsxs(Stack, {
|
|
175
|
+
children: [
|
|
176
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
177
|
+
variant: "body2",
|
|
178
|
+
children: `The ${brand} start-up screen`
|
|
179
|
+
}),
|
|
180
|
+
status.platformReady ? null : /*#__PURE__*/ _jsx(Typography, {
|
|
181
|
+
variant: "caption",
|
|
182
|
+
color: "text.secondary",
|
|
183
|
+
children: 'Until it is ready, your readers show their standard screen.'
|
|
184
|
+
})
|
|
185
|
+
]
|
|
186
|
+
})
|
|
187
|
+
}),
|
|
188
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
189
|
+
value: "logo",
|
|
190
|
+
disabled: Boolean(blockedReason) || !hasLogo,
|
|
191
|
+
control: /*#__PURE__*/ _jsx(Radio, {
|
|
192
|
+
size: "small"
|
|
193
|
+
}),
|
|
194
|
+
label: /*#__PURE__*/ _jsxs(Stack, {
|
|
195
|
+
children: [
|
|
196
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
197
|
+
variant: "body2",
|
|
198
|
+
children: 'Your logo'
|
|
199
|
+
}),
|
|
200
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
201
|
+
variant: "caption",
|
|
202
|
+
color: "text.secondary",
|
|
203
|
+
children: 'Your organization profile’s logo, centered on a white screen.'
|
|
204
|
+
})
|
|
205
|
+
]
|
|
206
|
+
})
|
|
207
|
+
}),
|
|
208
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
209
|
+
value: "image",
|
|
210
|
+
disabled: Boolean(blockedReason),
|
|
211
|
+
control: /*#__PURE__*/ _jsx(Radio, {
|
|
212
|
+
size: "small"
|
|
213
|
+
}),
|
|
214
|
+
label: /*#__PURE__*/ _jsxs(Stack, {
|
|
215
|
+
children: [
|
|
216
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
217
|
+
variant: "body2",
|
|
218
|
+
children: 'Your own image'
|
|
219
|
+
}),
|
|
220
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
221
|
+
variant: "caption",
|
|
222
|
+
color: "text.secondary",
|
|
223
|
+
children: `A full-screen portrait image from your media library, at least ${POS_READER_SPLASH_LARGEST.width} × ${POS_READER_SPLASH_LARGEST.height} pixels.`
|
|
224
|
+
})
|
|
225
|
+
]
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
]
|
|
229
|
+
}),
|
|
230
|
+
hasLogo ? null : /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
231
|
+
href: profileHref,
|
|
232
|
+
actionLabel: "Organization profile",
|
|
233
|
+
severity: status.effectiveChoice === 'logo' ? 'warning' : 'info',
|
|
234
|
+
children: 'Add a logo to your organization profile to show it on your card readers.'
|
|
235
|
+
}),
|
|
236
|
+
status.effectiveChoice === 'image' ? /*#__PURE__*/ _jsx(Box, {
|
|
237
|
+
children: /*#__PURE__*/ _jsx(BlockedControl, {
|
|
238
|
+
blocked: Boolean(blockedReason),
|
|
239
|
+
reason: blockedReason != null ? blockedReason : '',
|
|
240
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
241
|
+
size: "small",
|
|
242
|
+
onClick: ()=>void chooseImage(),
|
|
243
|
+
children: status.image ? 'Choose another image' : 'Choose image'
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
}) : null,
|
|
247
|
+
preview ? /*#__PURE__*/ _jsx(Box, {
|
|
248
|
+
component: "img",
|
|
249
|
+
src: preview,
|
|
250
|
+
alt: "",
|
|
251
|
+
sx: (theme)=>({
|
|
252
|
+
alignSelf: 'flex-start',
|
|
253
|
+
maxWidth: theme.spacing(16),
|
|
254
|
+
maxHeight: theme.spacing(16),
|
|
255
|
+
objectFit: 'contain',
|
|
256
|
+
borderRadius: 1,
|
|
257
|
+
border: 1,
|
|
258
|
+
borderColor: 'divider'
|
|
259
|
+
})
|
|
260
|
+
}) : null,
|
|
261
|
+
status.problem && !(status.effectiveChoice === 'logo' && !hasLogo) ? /*#__PURE__*/ _jsx(Alert, {
|
|
262
|
+
severity: "warning",
|
|
263
|
+
children: status.problem
|
|
264
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
265
|
+
variant: "caption",
|
|
266
|
+
color: "text.secondary",
|
|
267
|
+
children: !status.locationReady ? 'Your readers show it once you add your first one.' : status.appliedAtMs ? `Sent to your readers ${new Date(status.appliedAtMs).toLocaleString()}. Readers update within about ten minutes.` : 'Readers update within about ten minutes of a change.'
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
PosReaderSplashSetting.displayName = 'PosReaderSplashSetting';
|
|
273
|
+
export default PosReaderSplashSetting;
|
|
274
|
+
|
|
275
|
+
//# sourceMappingURL=pos-reader-splash-setting.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos-reader-splash-setting.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 { useMediaPicker, type PickedMedia } from '@aglyn/aglyn/app-utils/media-picker-context'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useConsoleHostRoute, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n FormControlLabel,\n Radio,\n RadioGroup,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n POS_READER_SPLASH_LARGEST,\n posReaderSplashArtworkProblem,\n type PosReaderSplashChoice,\n type PosReaderSplashStatus,\n} from '../../model/pos-reader-splash'\nimport {\n PosReaderSplashRequestError,\n readerSplashStatus,\n saveReaderSplash,\n} from './pos/pos-reader-splash-api'\n\nexport interface PosReaderSplashSettingProps {\n hostId: string\n /** Whether the viewer may change the site's readers (`managePos`, admin or editor). */\n canManagePos: boolean\n}\n\n/** An image's pixel size, read in the browser before anything is saved. */\nfunction imageSize(src: string): Promise<{ width: number; height: number } | null> {\n return new Promise((resolve) => {\n const image = new Image()\n image.onload = () => resolve({ width: image.naturalWidth, height: image.naturalHeight })\n image.onerror = () => resolve(null)\n image.src = src\n })\n}\n\nconst NOT_ALLOWED = 'Only an admin or editor with the \"Use the point of sale\" permission can change the readers.'\n\n/**\n * \"Reader start-up screen\" on the POS devices card (AGL-4048): what the\n * store's Stripe smart readers show while they wait for a payment. The\n * platform's default screen, the workspace's logo on a white screen, or a\n * full-screen portrait image from the media library. A white-label\n * workspace is never offered the platform's screen and shows its own logo\n * unless it picks an image.\n *\n * The server applies the choice to the store's readers when it is saved\n * (and again when this opens and finds the logo changed since); readers\n * pick it up within about ten minutes.\n */\nexport function PosReaderSplashSetting({ hostId, canManagePos }: PosReaderSplashSettingProps) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { pickMedia } = useMediaPicker()\n const { orgSlug } = useConsoleHostRoute(hostId)\n const [status, setStatus] = useState<PosReaderSplashStatus | null>(null)\n const [busy, setBusy] = useState(false)\n const [picked, setPicked] = useState<PickedMedia | null>(null)\n const uid = user?.uid ?? ''\n\n useEffect(() => {\n if (!user) return\n let active = true\n readerSplashStatus(user, hostId)\n .then((answer) => {\n if (active) setStatus(answer)\n })\n .catch(() => {\n if (active) setStatus(null)\n })\n return () => {\n active = false\n }\n // Keyed on the person, not the session object (see the register page).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uid, hostId])\n\n const save = useCallback(\n async (choice: PosReaderSplashChoice, image?: { mediaScope: string; mediaId: string }) => {\n if (!user) return\n setBusy(true)\n try {\n const answer = await saveReaderSplash(user, hostId, choice, image)\n setStatus(answer)\n if (!answer.problem) {\n enqueueSnackbar(\n answer.locationReady\n ? 'Saved. Your readers show it within about ten minutes.'\n : 'Saved. Your readers show it once you add one.',\n { variant: 'success', persist: false },\n )\n }\n } catch (error) {\n enqueueSnackbar(\n error instanceof PosReaderSplashRequestError ? error.message : 'The start-up screen could not be saved',\n { variant: 'warning', persist: false },\n )\n } finally {\n setBusy(false)\n }\n },\n [user, hostId, enqueueSnackbar],\n )\n\n const chooseImage = async () => {\n const media = await pickMedia?.({ kind: 'image' })\n if (!media) return\n if (!media.mediaId || !media.mediaScope) {\n enqueueSnackbar('Choose an image from your media library.', { variant: 'warning', persist: false })\n return\n }\n const size = await imageSize(media.url)\n const problem = posReaderSplashArtworkProblem({\n contentType: media.contentType ?? null,\n width: size?.width ?? 0,\n height: size?.height ?? 0,\n })\n if (problem) {\n enqueueSnackbar(problem, { variant: 'warning', persist: false })\n return\n }\n setPicked(media)\n await save('image', { mediaScope: media.mediaScope, mediaId: media.mediaId })\n }\n\n if (!status?.available) return null\n\n const blockedReason = !canManagePos ? NOT_ALLOWED : busy ? 'Saving…' : null\n const hasLogo = Boolean(status.logoUrl)\n const profileHref = orgSlug ? `/${orgSlug}/settings/profile` : undefined\n const brand = status.productName\n const preview =\n status.effectiveChoice === 'logo' ? status.logoUrl : status.effectiveChoice === 'image' ? (picked?.url ?? status.image?.url ?? null) : null\n\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Reader start-up screen'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'What your card readers show while they wait for a payment.'}\n </Typography>\n <RadioGroup\n value={status.effectiveChoice}\n onChange={(event) => {\n const choice = event.target.value as PosReaderSplashChoice\n if (choice === 'image') {\n if (status.image) void save('image', { mediaScope: status.image.mediaScope, mediaId: status.image.mediaId })\n else void chooseImage()\n return\n }\n void save(choice)\n }}\n >\n {status.whiteLabel ? null : (\n <FormControlLabel\n value=\"platform\"\n disabled={Boolean(blockedReason)}\n control={<Radio size=\"small\" />}\n label={\n <Stack>\n <Typography variant=\"body2\">{`The ${brand} start-up screen`}</Typography>\n {status.platformReady ? null : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Until it is ready, your readers show their standard screen.'}\n </Typography>\n )}\n </Stack>\n }\n />\n )}\n <FormControlLabel\n value=\"logo\"\n disabled={Boolean(blockedReason) || !hasLogo}\n control={<Radio size=\"small\" />}\n label={\n <Stack>\n <Typography variant=\"body2\">{'Your logo'}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Your organization profile’s logo, centered on a white screen.'}\n </Typography>\n </Stack>\n }\n />\n <FormControlLabel\n value=\"image\"\n disabled={Boolean(blockedReason)}\n control={<Radio size=\"small\" />}\n label={\n <Stack>\n <Typography variant=\"body2\">{'Your own image'}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`A full-screen portrait image from your media library, at least ${POS_READER_SPLASH_LARGEST.width} × ${POS_READER_SPLASH_LARGEST.height} pixels.`}\n </Typography>\n </Stack>\n }\n />\n </RadioGroup>\n {hasLogo ? null : (\n <PrerequisiteNotice\n href={profileHref}\n actionLabel=\"Organization profile\"\n severity={status.effectiveChoice === 'logo' ? 'warning' : 'info'}\n >\n {'Add a logo to your organization profile to show it on your card readers.'}\n </PrerequisiteNotice>\n )}\n {status.effectiveChoice === 'image' ? (\n <Box>\n <BlockedControl blocked={Boolean(blockedReason)} reason={blockedReason ?? ''}>\n <Button size=\"small\" onClick={() => void chooseImage()}>\n {status.image ? 'Choose another image' : 'Choose image'}\n </Button>\n </BlockedControl>\n </Box>\n ) : null}\n {preview ? (\n <Box\n component=\"img\"\n src={preview}\n alt=\"\"\n sx={(theme) => ({\n alignSelf: 'flex-start',\n maxWidth: theme.spacing(16),\n maxHeight: theme.spacing(16),\n objectFit: 'contain',\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n })}\n />\n ) : null}\n {status.problem && !(status.effectiveChoice === 'logo' && !hasLogo) ? (\n <Alert severity=\"warning\">{status.problem}</Alert>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {!status.locationReady\n ? 'Your readers show it once you add your first one.'\n : status.appliedAtMs\n ? `Sent to your readers ${new Date(status.appliedAtMs).toLocaleString()}. Readers update within about ten minutes.`\n : 'Readers update within about ten minutes of a change.'}\n </Typography>\n )}\n </Stack>\n )\n}\nPosReaderSplashSetting.displayName = 'PosReaderSplashSetting'\n\nexport default PosReaderSplashSetting\n"],"names":["useMediaPicker","BlockedControl","PrerequisiteNotice","useSnackbar","useConsoleHostRoute","useUser","Alert","Box","Button","FormControlLabel","Radio","RadioGroup","Stack","Typography","useCallback","useEffect","useState","POS_READER_SPLASH_LARGEST","posReaderSplashArtworkProblem","PosReaderSplashRequestError","readerSplashStatus","saveReaderSplash","imageSize","src","Promise","resolve","image","Image","onload","width","naturalWidth","height","naturalHeight","onerror","NOT_ALLOWED","PosReaderSplashSetting","hostId","canManagePos","picked","status","data","user","enqueueSnackbar","pickMedia","orgSlug","setStatus","busy","setBusy","setPicked","uid","active","then","answer","catch","save","choice","problem","locationReady","variant","persist","error","message","chooseImage","media","kind","mediaId","mediaScope","size","url","contentType","available","blockedReason","hasLogo","Boolean","logoUrl","profileHref","undefined","brand","productName","preview","effectiveChoice","spacing","color","value","onChange","event","target","whiteLabel","disabled","control","label","platformReady","href","actionLabel","severity","blocked","reason","onClick","component","alt","sx","theme","alignSelf","maxWidth","maxHeight","objectFit","borderRadius","border","borderColor","appliedAtMs","Date","toLocaleString","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ConsoleStaffPageProps } from '@aglyn/aglyn';
|
|
2
|
+
/**
|
|
3
|
+
* Staff → Reader start-up screen (AGL-4048): the platform's default screen
|
|
4
|
+
* for Stripe smart readers, shown by every store that has not picked its own
|
|
5
|
+
* logo or image. White-label workspaces never show it. The artwork is made
|
|
6
|
+
* separately and uploaded here, one image per screen size, each checked
|
|
7
|
+
* against Stripe's rules before it is kept. A change is put on every store's
|
|
8
|
+
* readers straight away; readers pick it up within about ten minutes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function PosReaderSplashStaffPage(props: ConsoleStaffPageProps): import("react").JSX.Element;
|
|
11
|
+
export declare namespace PosReaderSplashStaffPage {
|
|
12
|
+
var displayName: string;
|
|
13
|
+
}
|
|
14
|
+
export default PosReaderSplashStaffPage;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
19
|
+
import { resolveStaffRoleGate } from "@aglyn/aglyn/app-utils/staff-role-gate";
|
|
20
|
+
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
22
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
23
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
24
|
+
import { Alert, Box, Button, Stack, Typography } from "@mui/material";
|
|
25
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
26
|
+
import { POS_READER_SPLASH_MAX_BYTES } from "../../model/pos-reader-splash.js";
|
|
27
|
+
import { PosReaderSplashRequestError, staffApplyReaderSplash, staffReaderSplash, staffReaderSplashPreview, staffRemoveReaderSplash, staffUploadReaderSplash } from "./pos/pos-reader-splash-api.js";
|
|
28
|
+
const WRITE_ROLES = [
|
|
29
|
+
'super'
|
|
30
|
+
];
|
|
31
|
+
/** A file as base64, without the `data:` prefix. */ function readBase64(file) {
|
|
32
|
+
return new Promise((resolve, reject)=>{
|
|
33
|
+
const reader = new FileReader();
|
|
34
|
+
reader.onload = ()=>{
|
|
35
|
+
var _reader_result;
|
|
36
|
+
return resolve(String((_reader_result = reader.result) != null ? _reader_result : '').replace(/^data:[^,]*,/, ''));
|
|
37
|
+
};
|
|
38
|
+
reader.onerror = ()=>reject(reader.error);
|
|
39
|
+
reader.readAsDataURL(file);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Staff → Reader start-up screen (AGL-4048): the platform's default screen
|
|
44
|
+
* for Stripe smart readers, shown by every store that has not picked its own
|
|
45
|
+
* logo or image. White-label workspaces never show it. The artwork is made
|
|
46
|
+
* separately and uploaded here, one image per screen size, each checked
|
|
47
|
+
* against Stripe's rules before it is kept. A change is put on every store's
|
|
48
|
+
* readers straight away; readers pick it up within about ten minutes.
|
|
49
|
+
*/ export function PosReaderSplashStaffPage(props) {
|
|
50
|
+
var _ref, _ref1, _gate_reason, _ref2;
|
|
51
|
+
const { staffRole = null } = props;
|
|
52
|
+
const { data: user } = useUser();
|
|
53
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
54
|
+
const { confirm } = useConfirmationContext();
|
|
55
|
+
const [state, setState] = useState(null);
|
|
56
|
+
const [error, setError] = useState(null);
|
|
57
|
+
const [busy, setBusy] = useState(false);
|
|
58
|
+
const [previews, setPreviews] = useState({});
|
|
59
|
+
const inputs = useRef({});
|
|
60
|
+
const gate = resolveStaffRoleGate(staffRole, WRITE_ROLES);
|
|
61
|
+
const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
|
|
62
|
+
const warn = useCallback((caught, fallback)=>enqueueSnackbar(caught instanceof PosReaderSplashRequestError ? caught.message : fallback, {
|
|
63
|
+
variant: 'warning',
|
|
64
|
+
persist: false
|
|
65
|
+
}), [
|
|
66
|
+
enqueueSnackbar
|
|
67
|
+
]);
|
|
68
|
+
const load = useCallback(async ()=>{
|
|
69
|
+
if (!user) return;
|
|
70
|
+
setError(null);
|
|
71
|
+
try {
|
|
72
|
+
setState(await staffReaderSplash(user));
|
|
73
|
+
} catch (caught) {
|
|
74
|
+
setError(caught instanceof PosReaderSplashRequestError ? caught.message : 'The start-up screen could not be read');
|
|
75
|
+
}
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
}, [
|
|
78
|
+
uid
|
|
79
|
+
]);
|
|
80
|
+
useEffect(()=>{
|
|
81
|
+
void load();
|
|
82
|
+
}, [
|
|
83
|
+
load
|
|
84
|
+
]);
|
|
85
|
+
// Each family's image, fetched with the staff session (the route is not public).
|
|
86
|
+
const shas = (_ref1 = state == null ? void 0 : state.families.map((family)=>{
|
|
87
|
+
var _ref;
|
|
88
|
+
var _family_image;
|
|
89
|
+
return (_ref = (_family_image = family.image) == null ? void 0 : _family_image.sha256) != null ? _ref : '';
|
|
90
|
+
}).join(',')) != null ? _ref1 : '';
|
|
91
|
+
useEffect(()=>{
|
|
92
|
+
if (!user || !state) return;
|
|
93
|
+
let active = true;
|
|
94
|
+
const made = [];
|
|
95
|
+
void Promise.all(state.families.map(async (family)=>{
|
|
96
|
+
const url = family.image ? await staffReaderSplashPreview(user, family.id).catch(()=>null) : null;
|
|
97
|
+
if (url) made.push(url);
|
|
98
|
+
return [
|
|
99
|
+
family.id,
|
|
100
|
+
url != null ? url : undefined
|
|
101
|
+
];
|
|
102
|
+
})).then((entries)=>{
|
|
103
|
+
if (active) setPreviews(Object.fromEntries(entries));
|
|
104
|
+
});
|
|
105
|
+
return ()=>{
|
|
106
|
+
active = false;
|
|
107
|
+
made.forEach((url)=>URL.revokeObjectURL(url));
|
|
108
|
+
};
|
|
109
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
110
|
+
}, [
|
|
111
|
+
uid,
|
|
112
|
+
shas
|
|
113
|
+
]);
|
|
114
|
+
const applyEverywhere = async ()=>{
|
|
115
|
+
if (!user) return;
|
|
116
|
+
try {
|
|
117
|
+
const counts = await staffApplyReaderSplash(user);
|
|
118
|
+
enqueueSnackbar(`Updated ${counts.applied} ${counts.applied === 1 ? 'store' : 'stores'}` + (counts.failed ? `; ${counts.failed} could not be updated` : '') + (counts.more ? '. Press Update stores again for the rest.' : '.'), {
|
|
119
|
+
variant: counts.failed ? 'warning' : 'success',
|
|
120
|
+
persist: false
|
|
121
|
+
});
|
|
122
|
+
} catch (caught) {
|
|
123
|
+
warn(caught, 'Stores could not be updated');
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const change = async (run, fallback)=>{
|
|
127
|
+
setBusy(true);
|
|
128
|
+
try {
|
|
129
|
+
setState(await run());
|
|
130
|
+
await applyEverywhere();
|
|
131
|
+
} catch (caught) {
|
|
132
|
+
warn(caught, fallback);
|
|
133
|
+
} finally{
|
|
134
|
+
setBusy(false);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const upload = async (family, file)=>{
|
|
138
|
+
if (!user || !file) return;
|
|
139
|
+
if (file.size >= POS_READER_SPLASH_MAX_BYTES) {
|
|
140
|
+
enqueueSnackbar('Use an image under 2 MB.', {
|
|
141
|
+
variant: 'warning',
|
|
142
|
+
persist: false
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const data = await readBase64(file);
|
|
147
|
+
await change(()=>staffUploadReaderSplash(user, family.id, data), 'The image could not be uploaded');
|
|
148
|
+
};
|
|
149
|
+
const remove = async (family)=>{
|
|
150
|
+
if (!user) return;
|
|
151
|
+
const confirmed = await confirm({
|
|
152
|
+
title: `Remove the ${family.label} screen?`,
|
|
153
|
+
description: family.id === 'portrait-large' ? 'Without it there is no default: stores that use it go back to the reader’s standard screen.' : 'This screen is then drawn from the S700 and S710 image.',
|
|
154
|
+
confirmationText: 'Remove',
|
|
155
|
+
confirmationButtonProps: {
|
|
156
|
+
color: 'error'
|
|
157
|
+
}
|
|
158
|
+
}).then(()=>true).catch(()=>false);
|
|
159
|
+
if (confirmed) await change(()=>staffRemoveReaderSplash(user, family.id), 'The image could not be removed');
|
|
160
|
+
};
|
|
161
|
+
const writeReason = (_gate_reason = gate.reason) != null ? _gate_reason : busy ? 'Saving…' : undefined;
|
|
162
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
163
|
+
header: 'Reader start-up screen',
|
|
164
|
+
help: pluginDocsHelp('posHardware', {
|
|
165
|
+
anchor: '#reader-start-up-screen'
|
|
166
|
+
}),
|
|
167
|
+
HeaderProps: {
|
|
168
|
+
action: /*#__PURE__*/ _jsx(BlockedControl, {
|
|
169
|
+
blocked: Boolean(writeReason) || !(state == null ? void 0 : state.ready),
|
|
170
|
+
reason: writeReason != null ? writeReason : 'Upload the S700 and S710 screen first.',
|
|
171
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
172
|
+
size: "small",
|
|
173
|
+
onClick: ()=>void applyEverywhere(),
|
|
174
|
+
children: 'Update stores'
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
contentGutterX: true,
|
|
179
|
+
contentGutterY: true,
|
|
180
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
181
|
+
spacing: 2,
|
|
182
|
+
children: [
|
|
183
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
184
|
+
variant: "body2",
|
|
185
|
+
color: "text.secondary",
|
|
186
|
+
children: 'The default screen Stripe smart readers show while they wait for a payment, on every store that has not ' + 'chosen its own logo or image. White-label workspaces never show it. Readers pick up a change within about ' + 'ten minutes.'
|
|
187
|
+
}),
|
|
188
|
+
/*#__PURE__*/ _jsx(Alert, {
|
|
189
|
+
severity: "info",
|
|
190
|
+
children: 'Each image must be a PNG or JPEG under 2 MB, at exactly the reader’s screen size. The WisePOS E screen is ' + 'drawn from the S700 and S710 image when none is uploaded.'
|
|
191
|
+
}),
|
|
192
|
+
error ? /*#__PURE__*/ _jsx(Alert, {
|
|
193
|
+
severity: "warning",
|
|
194
|
+
children: error
|
|
195
|
+
}) : null,
|
|
196
|
+
((_ref2 = state == null ? void 0 : state.families) != null ? _ref2 : []).map((family)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
197
|
+
direction: {
|
|
198
|
+
xs: 'column',
|
|
199
|
+
sm: 'row'
|
|
200
|
+
},
|
|
201
|
+
spacing: 2,
|
|
202
|
+
sx: {
|
|
203
|
+
alignItems: {
|
|
204
|
+
sm: 'center'
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
children: [
|
|
208
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
209
|
+
sx: (theme)=>({
|
|
210
|
+
width: theme.spacing(12),
|
|
211
|
+
aspectRatio: '9 / 16',
|
|
212
|
+
borderRadius: 1,
|
|
213
|
+
border: 1,
|
|
214
|
+
borderColor: 'divider',
|
|
215
|
+
bgcolor: 'action.hover',
|
|
216
|
+
overflow: 'hidden',
|
|
217
|
+
flexShrink: 0
|
|
218
|
+
}),
|
|
219
|
+
children: previews[family.id] ? /*#__PURE__*/ _jsx(Box, {
|
|
220
|
+
component: "img",
|
|
221
|
+
src: previews[family.id],
|
|
222
|
+
alt: `${family.label} start-up screen`,
|
|
223
|
+
sx: {
|
|
224
|
+
width: 1,
|
|
225
|
+
height: 1,
|
|
226
|
+
objectFit: 'cover',
|
|
227
|
+
display: 'block'
|
|
228
|
+
}
|
|
229
|
+
}) : null
|
|
230
|
+
}),
|
|
231
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
232
|
+
spacing: 0.5,
|
|
233
|
+
sx: {
|
|
234
|
+
flex: 1,
|
|
235
|
+
minWidth: 0
|
|
236
|
+
},
|
|
237
|
+
children: [
|
|
238
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
239
|
+
variant: "subtitle2",
|
|
240
|
+
children: family.label
|
|
241
|
+
}),
|
|
242
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
243
|
+
variant: "body2",
|
|
244
|
+
color: "text.secondary",
|
|
245
|
+
children: `${family.width} × ${family.height} pixels. ` + (family.image ? `Uploaded ${new Date(family.image.uploadedAtMs).toLocaleString()}.` : family.id === 'portrait-large' ? 'Not uploaded: stores show the reader’s standard screen.' : 'Not uploaded: drawn from the S700 and S710 image.')
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
}),
|
|
249
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
250
|
+
direction: "row",
|
|
251
|
+
spacing: 1,
|
|
252
|
+
children: [
|
|
253
|
+
/*#__PURE__*/ _jsx("input", {
|
|
254
|
+
ref: (element)=>{
|
|
255
|
+
inputs.current[family.id] = element;
|
|
256
|
+
},
|
|
257
|
+
type: "file",
|
|
258
|
+
accept: "image/png,image/jpeg",
|
|
259
|
+
hidden: true,
|
|
260
|
+
"aria-label": `Upload the ${family.label} screen`,
|
|
261
|
+
onChange: (event)=>{
|
|
262
|
+
var _event_target_files;
|
|
263
|
+
const file = (_event_target_files = event.target.files) == null ? void 0 : _event_target_files[0];
|
|
264
|
+
event.target.value = '';
|
|
265
|
+
void upload(family, file);
|
|
266
|
+
}
|
|
267
|
+
}),
|
|
268
|
+
/*#__PURE__*/ _jsx(BlockedControl, {
|
|
269
|
+
blocked: Boolean(writeReason),
|
|
270
|
+
reason: writeReason != null ? writeReason : '',
|
|
271
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
272
|
+
size: "small",
|
|
273
|
+
onClick: ()=>{
|
|
274
|
+
var _inputs_current_family_id;
|
|
275
|
+
return (_inputs_current_family_id = inputs.current[family.id]) == null ? void 0 : _inputs_current_family_id.click();
|
|
276
|
+
},
|
|
277
|
+
children: family.image ? 'Replace' : 'Upload'
|
|
278
|
+
})
|
|
279
|
+
}),
|
|
280
|
+
family.image ? /*#__PURE__*/ _jsx(BlockedControl, {
|
|
281
|
+
blocked: Boolean(writeReason),
|
|
282
|
+
reason: writeReason != null ? writeReason : '',
|
|
283
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
284
|
+
size: "small",
|
|
285
|
+
color: "error",
|
|
286
|
+
onClick: ()=>void remove(family),
|
|
287
|
+
children: 'Remove'
|
|
288
|
+
})
|
|
289
|
+
}) : null
|
|
290
|
+
]
|
|
291
|
+
})
|
|
292
|
+
]
|
|
293
|
+
}, family.id))
|
|
294
|
+
]
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
PosReaderSplashStaffPage.displayName = 'PosReaderSplashStaffPage';
|
|
299
|
+
export default PosReaderSplashStaffPage;
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=pos-reader-splash-staff-page.component.js.map
|