payment ux refinement
Build and Push Lunchtime Images (Kaniko) / build-and-push (push) Successful in 1m37s
Build and Push Lunchtime Images (Kaniko) / build-and-push (push) Successful in 1m37s
This commit is contained in:
1 parent
e53f814451
commit
616ac5583f
2 files changed
+81
-84
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Checkbox, Result, Space, Tooltip, Typography } from "antd";
|
||||
import { Button, Flex, Result, Space, Tag, Tooltip, Typography, theme } from "antd";
|
||||
import { CheckCircleFilled, CheckOutlined, WalletOutlined } from "@ant-design/icons";
|
||||
import type { Submission } from "../../lib/types";
|
||||
|
||||
const { Text } = Typography;
|
||||
@@ -22,12 +23,17 @@ export default function ParticipantSubmissionSummary({
|
||||
savedOrderString,
|
||||
savedEstimatedTotal,
|
||||
isClosed,
|
||||
onHowToPay,
|
||||
}: {
|
||||
submission: Submission | null;
|
||||
savedOrderString: string;
|
||||
savedEstimatedTotal: string | null;
|
||||
isClosed: boolean;
|
||||
/** Shown as a "How to pay" button when payment details exist. */
|
||||
onHowToPay?: () => void;
|
||||
}) {
|
||||
const { token } = theme.useToken();
|
||||
|
||||
if (!submission) {
|
||||
return (
|
||||
<Result
|
||||
@@ -42,44 +48,55 @@ export default function ParticipantSubmissionSummary({
|
||||
);
|
||||
}
|
||||
|
||||
const statuses = [
|
||||
{ label: "Accepted", done: submission.accepted },
|
||||
{ label: "Paid", done: submission.paid },
|
||||
{ label: "Received", done: !!submission.handed_out },
|
||||
];
|
||||
|
||||
return (
|
||||
<Result
|
||||
status="success"
|
||||
title="Order submitted"
|
||||
subTitle={
|
||||
<Space direction="vertical">
|
||||
<Tooltip title="Your submitted order">
|
||||
<Text code copyable>
|
||||
{savedOrderString}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Flex vertical gap={8}>
|
||||
<Flex justify="space-between" align="center" wrap="wrap" gap={8}>
|
||||
<Text strong>
|
||||
<CheckCircleFilled style={{ color: token.colorSuccess }} /> Order submitted
|
||||
</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
{statuses.map(({ label, done }) => (
|
||||
<Tag
|
||||
key={label}
|
||||
bordered={false}
|
||||
color={done ? "success" : "default"}
|
||||
icon={done ? <CheckOutlined /> : undefined}
|
||||
style={{ marginInlineEnd: 0 }}
|
||||
>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Tooltip title="Your submitted order">
|
||||
<Text code copyable>
|
||||
{savedOrderString}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Flex justify="space-between" align="center" wrap="wrap" gap={8}>
|
||||
<Space size={4} direction="vertical">
|
||||
{savedEstimatedTotal && (
|
||||
<Tooltip title="Rough estimate based on configured item prices. Final amount may differ.">
|
||||
<Text type="secondary">Estimated total: {savedEstimatedTotal}</Text>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isClosed && <Text>Review or adjust at any time while the order is open</Text>}
|
||||
</Space>
|
||||
}
|
||||
extra={[
|
||||
<Space key="submission-status" direction="vertical" size={4}>
|
||||
<Text strong>Submission status</Text>
|
||||
<Space size={20} wrap>
|
||||
<Checkbox checked={submission.accepted} style={{ pointerEvents: "none" }}>
|
||||
Accepted
|
||||
</Checkbox>
|
||||
<Checkbox checked={submission.paid} style={{ pointerEvents: "none" }}>
|
||||
Paid
|
||||
</Checkbox>
|
||||
<Checkbox checked={!!submission.handed_out} style={{ pointerEvents: "none" }}>
|
||||
Received
|
||||
</Checkbox>
|
||||
</Space>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Updated {formatTimestamp(submission.updated_at)}
|
||||
{!isClosed && "You can adjust your order while it is open. "}Updated{" "}
|
||||
{formatTimestamp(submission.updated_at)}
|
||||
</Text>
|
||||
</Space>,
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
{onHowToPay && !submission.paid && (
|
||||
<Button size="small" type="primary" icon={<WalletOutlined />} onClick={onHowToPay}>
|
||||
How to pay
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Popconfirm,
|
||||
Row,
|
||||
Space,
|
||||
Steps,
|
||||
Tooltip,
|
||||
Typography,
|
||||
message,
|
||||
@@ -20,7 +19,7 @@ import { navigateTo } from "../lib/routing";
|
||||
import { apiService } from "../lib/services";
|
||||
import LoadingSkeleton from "../components/utils/LoadingSkeleton";
|
||||
import ReadOnlyOrderOverviewCard from "../components/orders/ReadOnlyOrderOverviewCard";
|
||||
import PaymentDetailsCard from "../components/orders/PaymentDetailsCard";
|
||||
import { PaymentMethodsAccordion } from "../components/orders/PaymentDetailsCard";
|
||||
import { normalizePaymentDetails, type PaymentContext } from "../lib/paymentDetails";
|
||||
import {
|
||||
buildFormattedOrderString,
|
||||
@@ -42,7 +41,7 @@ import ParticipantDraftSummaryAlert from "../components/views/ParticipantDraftSu
|
||||
|
||||
const { Text, Link } = Typography;
|
||||
|
||||
const PAYMENT_CARD_ID = "order-payment";
|
||||
const OVERVIEW_CARD_ID = "order-overview";
|
||||
|
||||
type SubmissionValues = {
|
||||
choices: NormalizedOrderSelections;
|
||||
@@ -91,6 +90,7 @@ type ParticipantLoadData = {
|
||||
export default function ParticipantView({ orderId }: { orderId: string }) {
|
||||
const [form] = Form.useForm<FormModel>();
|
||||
const watchedValues = Form.useWatch([], form);
|
||||
const [overviewTab, setOverviewTab] = useState("description");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [loadingSubmission, setLoadingSubmission] = useState(true);
|
||||
const [isEditingSubmittedOrder, setIsEditingSubmittedOrder] = useState(false);
|
||||
@@ -414,26 +414,33 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
||||
amount: existingSubmission ? savedEstimatedAmount : null,
|
||||
};
|
||||
const hasPayment = paymentMethods.length > 0;
|
||||
// Payment details are only revealed once the participant has placed an order.
|
||||
const showPaymentSection = hasPayment && !!existingSubmission;
|
||||
const paymentTabs = hasPayment
|
||||
? [
|
||||
{
|
||||
key: "payment",
|
||||
label: "Payment",
|
||||
icon: <WalletOutlined />,
|
||||
children: (
|
||||
<Space direction="vertical" size={12} style={{ width: "100%" }}>
|
||||
{!existingSubmission && (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="Place your order first so the correct amount is shown here."
|
||||
/>
|
||||
)}
|
||||
<PaymentMethodsAccordion methods={paymentMethods} context={paymentContext} />
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const openPaymentSection = () => {
|
||||
document.getElementById(PAYMENT_CARD_ID)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
const openPaymentTab = () => {
|
||||
setOverviewTab("payment");
|
||||
document.getElementById(OVERVIEW_CARD_ID)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
};
|
||||
|
||||
const isPaid = !!existingSubmission?.paid;
|
||||
const progressSteps = [
|
||||
{ title: "Place order", description: "Choose and submit" },
|
||||
...(hasPayment ? [{ title: "Pay", description: "Transfer the amount" }] : []),
|
||||
{ title: "Done", description: hasPayment ? "Payment received" : "Order placed" },
|
||||
];
|
||||
const doneStep = progressSteps.length - 1;
|
||||
const currentStep = !existingSubmission
|
||||
? 0
|
||||
: hasPayment && !isPaid
|
||||
? 1
|
||||
: doneStep;
|
||||
|
||||
const canShowSubmittedResult =
|
||||
!!existingSubmission && !isEditingSubmittedOrder;
|
||||
const showSummaryView = isClosed || canShowSubmittedResult;
|
||||
@@ -478,18 +485,14 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<Steps
|
||||
size="small"
|
||||
responsive
|
||||
current={currentStep}
|
||||
status={currentStep === doneStep && existingSubmission ? "finish" : "process"}
|
||||
items={progressSteps}
|
||||
/>
|
||||
|
||||
<ReadOnlyOrderOverviewCard
|
||||
id={OVERVIEW_CARD_ID}
|
||||
order={liveOrder}
|
||||
config={config}
|
||||
hideMenu
|
||||
extraTabs={paymentTabs}
|
||||
activeTabKey={overviewTab}
|
||||
onTabChange={setOverviewTab}
|
||||
/>
|
||||
|
||||
<Card
|
||||
@@ -523,23 +526,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
||||
savedOrderString={savedOrderString}
|
||||
savedEstimatedTotal={savedEstimatedTotal}
|
||||
isClosed={isClosed}
|
||||
/>
|
||||
)}
|
||||
{showSummaryView && showPaymentSection && !isPaid && (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
icon={<WalletOutlined />}
|
||||
message={
|
||||
savedEstimatedTotal
|
||||
? `Your estimated total is ${savedEstimatedTotal}`
|
||||
: "Payment details are available for this order"
|
||||
}
|
||||
action={
|
||||
<Button size="small" type="primary" onClick={openPaymentSection}>
|
||||
How to pay
|
||||
</Button>
|
||||
}
|
||||
onHowToPay={hasPayment ? openPaymentTab : undefined}
|
||||
/>
|
||||
)}
|
||||
{!showSummaryView && (
|
||||
@@ -620,13 +607,6 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{showPaymentSection && (
|
||||
<PaymentDetailsCard
|
||||
id={PAYMENT_CARD_ID}
|
||||
payment={{ methods: paymentMethods }}
|
||||
context={paymentContext}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</AsyncContent>
|
||||
|
||||
Reference in new issue
Block a user