738 lines
21 KiB
Vue
738 lines
21 KiB
Vue
<script setup lang="ts">
|
|
import { useI18n } from "vue-i18n";
|
|
import { onMounted, computed, ref } from "vue";
|
|
import {
|
|
reset_all_values,
|
|
customer_name,
|
|
nextStep,
|
|
searchAndSelectCustomer,
|
|
isCustomerSelected,
|
|
order_id,
|
|
customer_id,
|
|
step,
|
|
reg_1,
|
|
reg_2,
|
|
reg_3,
|
|
reference,
|
|
order_notes,
|
|
order_safety_seal,
|
|
loadCustomerAttributes,
|
|
hasAttribute,
|
|
uploadAttachment,
|
|
restoreStoredPosOrderId,
|
|
} from "@/components/shop/POSDepartmentProcess.vue";
|
|
import * as POSDepartmentProcess from "@/components/shop/POSDepartmentProcess.vue";
|
|
import { errors } from "@/components/request/HandleGlobalError.vue";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
|
|
import ContinueArrow from "@/components/viewport/elements/icons/ContinueArrow.vue";
|
|
import {
|
|
vehicles,
|
|
metadata,
|
|
getCustomerId,
|
|
popups,
|
|
resetPos,
|
|
attachments,
|
|
transactionHistory,
|
|
transactionItems,
|
|
} from "../objects/PosDepartmentStepMobileFlow.vue";
|
|
import {
|
|
finalizeCurrentMobileOrder,
|
|
getResolvedMobileSafetySeal,
|
|
syncMobileSafetySealState,
|
|
} from "../objects/mobileOrderCompletion.js";
|
|
import Swal from "sweetalert2";
|
|
import LongPressListener from "@/components/viewport/elements/wrappers/LongPressListener.vue";
|
|
import { views } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
|
|
|
const { t } = useI18n();
|
|
const selectCustomerString = `${
|
|
SessionUser.objects.global.language.select
|
|
} ${SessionUser.objects.global.language.customer.toLowerCase()}`;
|
|
const isCreatingOrder = computed(() => POSDepartmentProcess.isCreatingOrder?.value ?? false);
|
|
|
|
const props = defineProps({
|
|
// If true, the button will have a white background. Default is true.
|
|
isWhite: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
isDark: {
|
|
type: Boolean,
|
|
default: false, // When set to true, this overrides the isWhite prop.
|
|
},
|
|
// Function that returns a Promise, to be executed before proceeding to the next step.
|
|
onBeforeStep: {
|
|
type: Function,
|
|
default: () => Promise.resolve(),
|
|
},
|
|
// Custom action will override the default onClick behavior, if provided.
|
|
customAction: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
// Custom is the button disabled state. If provided, this will override the default disabled logic.
|
|
customDisabled: {
|
|
type: Boolean,
|
|
default: null,
|
|
},
|
|
// Custom long press
|
|
customLongPress: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
// Button classes
|
|
buttonClasses: {
|
|
type: Array as () => string[],
|
|
default: () => [],
|
|
},
|
|
actionKey: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
copyKey: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const updateReference = () => {
|
|
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.reference(order_id.value, reference.value);
|
|
};
|
|
|
|
const updateNotes = () => {
|
|
if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.notes(order_id.value, order_notes.value);
|
|
};
|
|
|
|
const updateSafetySeal = () => {
|
|
if (!!order_id.value && order_id.value > 0)
|
|
SessionUser.objects.orders.set.safety_seal(order_id.value, order_safety_seal.value);
|
|
};
|
|
|
|
// Helpers: extracted for clarity and reuse
|
|
const isNonEmptyString = (val) => typeof val === "string" && val.trim().length > 0;
|
|
|
|
const resolveReferenceFromSources = () => {
|
|
// 1) Metadata reference
|
|
const metaRef = metadata.getReference?.();
|
|
if (isNonEmptyString(metaRef)) return metaRef.trim();
|
|
|
|
// 2) Local reactive reference field
|
|
if (isNonEmptyString(reference.value)) return reference.value.trim();
|
|
|
|
// 3) Vehicle #1 reference (fallback)
|
|
const vehicleRef = vehicles.get(1)?.reference;
|
|
if (isNonEmptyString(vehicleRef)) return String(vehicleRef).trim();
|
|
|
|
return null;
|
|
};
|
|
|
|
const promptForReference = async (initialValue = "") => {
|
|
const result = await Swal.fire({
|
|
title: t("admin.pos.reference_required_title"),
|
|
text: t("admin.pos.reference_required_text"),
|
|
input: "text",
|
|
inputLabel: t("common.reference"),
|
|
inputValue: initialValue,
|
|
showCancelButton: true,
|
|
confirmButtonText: t("admin.pos.confirm"),
|
|
cancelButtonText: t("admin.pos.cancel"),
|
|
preConfirm: (newReference) => {
|
|
if (!isNonEmptyString(newReference)) {
|
|
Swal.showValidationMessage(t("admin.pos.reference_cannot_be_empty"));
|
|
}
|
|
return newReference;
|
|
},
|
|
});
|
|
if (result.isConfirmed && isNonEmptyString(result.value)) {
|
|
return result.value.trim();
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Refactored main function
|
|
const validateReferenceRequirements = async () => {
|
|
await loadCustomerAttributes();
|
|
|
|
const customerRequiresReference = hasAttribute("requiresReferenceNumber");
|
|
|
|
// Try to resolve an existing reference from known sources
|
|
const resolvedRef = resolveReferenceFromSources();
|
|
if (isNonEmptyString(resolvedRef)) {
|
|
// Ensure both local ref and metadata are synchronized
|
|
if (reference.value !== resolvedRef) reference.value = resolvedRef;
|
|
if (metadata.getReference() !== resolvedRef) metadata.setReference(resolvedRef);
|
|
return true;
|
|
}
|
|
|
|
// If customer does not require a reference, we are good even if none was resolved
|
|
if (!customerRequiresReference) {
|
|
return true;
|
|
}
|
|
|
|
// Prompt user for a reference if required and none available
|
|
try {
|
|
const enteredRef = await promptForReference(reference.value ?? "");
|
|
if (isNonEmptyString(enteredRef)) {
|
|
reference.value = enteredRef;
|
|
metadata.setReference(enteredRef);
|
|
console.warn("New reference set:", enteredRef);
|
|
return true;
|
|
}
|
|
console.warn("Reference number is required but was not provided. Aborting operation.");
|
|
return false;
|
|
} catch (error) {
|
|
console.warn("An error occurred while setting the reference number:", error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const step1 = async () => {
|
|
/** This function can be used to perform any specific actions for step 1 */
|
|
// Set registration numbers
|
|
reg_1.value = vehicles.vehicle_1.value?.reg || "";
|
|
reg_2.value = vehicles.vehicle_2.value?.reg || "";
|
|
reg_3.value = vehicles.vehicle_3.value?.reg || "";
|
|
// Reset the vehicle selection
|
|
views.vehicleSelection.value = false;
|
|
// Merge references and notes
|
|
mergeReferences();
|
|
mergeNotes();
|
|
mergeSafetySeal();
|
|
// We only need to check if the customer requires reference, if the field is empty.
|
|
if (!isNonEmptyString(reference.value)) {
|
|
const isValid = await validateReferenceRequirements();
|
|
if (!isValid) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Reference is resolved, persist metadata and pending attachments.
|
|
return finalizeStep1();
|
|
};
|
|
const mergeReferences = () => {
|
|
if (metadata.getReference() && metadata.getReference().length >= 1) {
|
|
// If there is no reference, set it to the registration number of vehicle 1
|
|
reference.value = metadata.getReference();
|
|
updateReference();
|
|
} else if (reference.value && reference.value.length >= 1) {
|
|
// If there is a reference in the field, set it to the metadata
|
|
metadata.setReference(reference.value);
|
|
updateReference();
|
|
} else if (vehicles.vehicle_1.value?.reference && vehicles.vehicle_1.value?.reference.length >= 1) {
|
|
// If there is no reference, set it to the registration number of vehicle 1
|
|
reference.value = vehicles.vehicle_1.value?.reference;
|
|
metadata.setReference(reference.value);
|
|
updateReference();
|
|
}
|
|
};
|
|
const mergeNotes = () => {
|
|
if (metadata.getNotes() && metadata.getNotes().length >= 1) {
|
|
order_notes.value = metadata.getNotes();
|
|
updateNotes();
|
|
} else if (order_notes.value && order_notes.value.length >= 1) {
|
|
metadata.setNotes(order_notes.value);
|
|
updateNotes();
|
|
}
|
|
};
|
|
const mergeSafetySeal = () => {
|
|
const resolvedSafetySeal = getResolvedMobileSafetySeal();
|
|
order_safety_seal.value = resolvedSafetySeal;
|
|
syncMobileSafetySealState(resolvedSafetySeal);
|
|
updateSafetySeal();
|
|
};
|
|
const finalizeStep1 = async () => {
|
|
mergeReferences();
|
|
mergeNotes();
|
|
mergeSafetySeal();
|
|
// Upload the attachments if there are any (And the order id has been created)
|
|
if (attachments.getBase64().length > 0 && order_id.value > 0) {
|
|
const uploadResults = await Promise.all(
|
|
attachments.getBase64().map(async (attachment: any) => {
|
|
try {
|
|
const didUpload = await uploadAttachment(attachment);
|
|
if (didUpload) {
|
|
console.warn("Attachment uploaded successfully");
|
|
attachments.removeBase64(attachment);
|
|
}
|
|
return didUpload;
|
|
} catch (error: any) {
|
|
errors.value.push(error);
|
|
console.warn("An error occurred while uploading the attachment:", error);
|
|
return false;
|
|
}
|
|
})
|
|
);
|
|
|
|
if (uploadResults.some((didUpload) => didUpload === false)) {
|
|
console.warn("One or more attachments could not be uploaded before completion.");
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const addTransactionToHistory = async (orderId: number) => {
|
|
SessionUser.objects.orders.get
|
|
.single(orderId)
|
|
.then((transaction: any) => {
|
|
if (transaction) {
|
|
transactionHistory.add(transaction);
|
|
console.warn("Transaction added to history:", transaction);
|
|
} else {
|
|
console.warn("Unable to add transaction to history: Order not found.");
|
|
}
|
|
})
|
|
.catch((error: any) => {
|
|
errors.value.push(error);
|
|
console.warn("An error occurred while fetching the transaction for history:", error);
|
|
});
|
|
};
|
|
|
|
const toPositiveInteger = (value: any) => {
|
|
const parsedValue = Number.parseInt(String(value ?? ""), 10);
|
|
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
|
|
};
|
|
|
|
const getSelectedBookingId = () => {
|
|
return toPositiveInteger(metadata.getBookingId?.() ?? metadata.bookingId?.value ?? null);
|
|
};
|
|
|
|
const completeStep2Order = async ({
|
|
bookingSafetySeal = null,
|
|
markOrderCompleted = false,
|
|
}: {
|
|
bookingSafetySeal?: string | null;
|
|
markOrderCompleted?: boolean;
|
|
} = {}) => {
|
|
await finalizeCurrentMobileOrder({
|
|
bookingSafetySeal,
|
|
markOrderCompleted,
|
|
});
|
|
popups.select("completed_transaction", {
|
|
message: `Order #${order_id.value} successfully created.`,
|
|
});
|
|
completeOrder();
|
|
};
|
|
|
|
const step2 = async () => {
|
|
/** This function can be used to perform any specific actions for step 2 */
|
|
// If the customer is paying with a card, go to the payment step
|
|
if (isCustomerSelected() && getCustomerId() === 999) {
|
|
nextStep({ isMobile: true });
|
|
return;
|
|
}
|
|
|
|
const selectedBookingId = getSelectedBookingId();
|
|
const hasWashCertificateInBasket = Boolean(transactionItems.containsWashCertificate?.());
|
|
const resolvedSafetySeal = getResolvedMobileSafetySeal();
|
|
const hasResolvedSafetySeal = isNonEmptyString(resolvedSafetySeal);
|
|
const requiresBookingCompletionPopup = Boolean(selectedBookingId && hasWashCertificateInBasket);
|
|
const shouldMarkOrderAsCompleted = !selectedBookingId && hasWashCertificateInBasket;
|
|
|
|
try {
|
|
if (requiresBookingCompletionPopup && hasResolvedSafetySeal) {
|
|
syncMobileSafetySealState(resolvedSafetySeal);
|
|
await completeStep2Order({
|
|
bookingSafetySeal: resolvedSafetySeal,
|
|
markOrderCompleted: false,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (requiresBookingCompletionPopup) {
|
|
const onCompleteBookingWithSafetySeal = async ({ safetySeal = "" } = {}) => {
|
|
syncMobileSafetySealState(safetySeal);
|
|
await completeStep2Order({
|
|
bookingSafetySeal: String(safetySeal ?? ""),
|
|
markOrderCompleted: false,
|
|
});
|
|
};
|
|
|
|
popups.select("complete_booking", {
|
|
props: {
|
|
safetySeal: getResolvedMobileSafetySeal(),
|
|
onCompleteWithCertificate: onCompleteBookingWithSafetySeal,
|
|
onCompleteWithoutCertificate: onCompleteBookingWithSafetySeal,
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
await completeStep2Order({
|
|
markOrderCompleted: shouldMarkOrderAsCompleted,
|
|
});
|
|
} catch (error: any) {
|
|
errors.value.push(error);
|
|
console.warn("An error occurred while completing the mobile order:", error);
|
|
popups.select("error");
|
|
}
|
|
};
|
|
|
|
const completeOrder = () => {
|
|
addTransactionToHistory(order_id.value);
|
|
setTimeout(() => {
|
|
// If the customer is not paying with a card, reset the POS state (After 3 seconds, to make sure the popup is visible)
|
|
reset_all_values(); // Reset all values
|
|
order_id.value = 0; // Reset the order ID
|
|
popups.clear(); // Clear the popup
|
|
resetPos(); // Reset the POS state
|
|
// Start the process again
|
|
}, 3000);
|
|
};
|
|
|
|
const step3 = () => {
|
|
/** This function can be used to perform any specific actions for step 3 */
|
|
// This step is not defined, but you can add your logic here if needed
|
|
console.warn("Step 3: Card payment step is not defined.");
|
|
};
|
|
|
|
const isProcessingClick = ref(false);
|
|
const isBusy = computed(() => metadata.getLoadingState() || isCreatingOrder.value || isProcessingClick.value);
|
|
|
|
const onClick = async () => {
|
|
// Check if the click is cancelled
|
|
if (isClickCancelled.value || isProcessingClick.value) {
|
|
return;
|
|
}
|
|
// Check if a custom action is provided
|
|
if (props.customAction) {
|
|
isProcessingClick.value = true;
|
|
try {
|
|
await Promise.resolve(props.customAction());
|
|
} finally {
|
|
isProcessingClick.value = false;
|
|
}
|
|
return;
|
|
}
|
|
if (!(isCustomerSelected() && getCustomerId() > 0)) {
|
|
// Show the popup to select a customer
|
|
popups.select("select_customer");
|
|
return;
|
|
}
|
|
|
|
isProcessingClick.value = true;
|
|
try {
|
|
await props.onBeforeStep();
|
|
// Proceed to the next step
|
|
switch (step.value) {
|
|
case 1:
|
|
if (!(await step1())) {
|
|
return;
|
|
}
|
|
const restoredOrderId = await restoreStoredPosOrderId({
|
|
validateOrder: true,
|
|
customerId: getCustomerId(),
|
|
departmentId: POSDepartmentProcess.getDepartment(),
|
|
allowCompleted: false,
|
|
syncDepartment: true,
|
|
});
|
|
if (restoredOrderId) {
|
|
console.warn("Order ID retrieved from local storage:", restoredOrderId);
|
|
nextStep({
|
|
isMobile: true,
|
|
orderCreation: false,
|
|
bookingId: metadata.getBookingId?.() ?? null,
|
|
});
|
|
return;
|
|
}
|
|
nextStep({
|
|
isMobile: true,
|
|
orderCreation: true,
|
|
bookingId: metadata.getBookingId?.() ?? null,
|
|
});
|
|
break;
|
|
case 2:
|
|
if (!(await step1())) {
|
|
return;
|
|
}
|
|
await step2();
|
|
break;
|
|
case 3:
|
|
// This step is not defined, but you can add your logic here if needed
|
|
step3();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
console.warn("Next-step action was interrupted:", error);
|
|
} finally {
|
|
isProcessingClick.value = false;
|
|
}
|
|
};
|
|
|
|
/** Synchronize customer selection state */
|
|
const applyCustomerSelection = () => {
|
|
let shouldApplyCustomerSelection = false;
|
|
if (!isCustomerSelected() && getCustomerId() > 0) {
|
|
// Search and select the customer
|
|
shouldApplyCustomerSelection = true;
|
|
}
|
|
if (isCustomerSelected() && parseInt(customer_id.value) !== getCustomerId()) {
|
|
// The customers are out of sync
|
|
shouldApplyCustomerSelection = true;
|
|
}
|
|
// If any checks failed, apply the customer selection.
|
|
if (shouldApplyCustomerSelection) {
|
|
searchAndSelectCustomer(getCustomerId());
|
|
}
|
|
};
|
|
|
|
// Check customer selection states.
|
|
onMounted(() => {
|
|
applyCustomerSelection();
|
|
});
|
|
|
|
const isRegistrationNumberFilled = () => {
|
|
return vehicles?.vehicle_1?.value?.reg && vehicles.vehicle_1.value?.reg.length >= 4;
|
|
};
|
|
// Is the requirements for clicking the button met?
|
|
const isRequirementsForClickMet = () => {
|
|
// If a custom disabled state is provided, use that.
|
|
if (props.customDisabled !== null) {
|
|
return !props.customDisabled;
|
|
}
|
|
/**
|
|
* States, and their requirements:
|
|
* If any popup is open, the button cannot be clicked.
|
|
* If the metadata state is loading, the button cannot be clicked.
|
|
* If the customer is not selected, the button can be clicked to open the customer selection popup.
|
|
* If the customer is selected, the button can be clicked if the reg_1 is filled. (>= 4 characters)
|
|
*/
|
|
// If any popup is open, the button cannot be clicked.
|
|
if (popups.isSet.value) {
|
|
return false;
|
|
}
|
|
// Prevent duplicate click handling while one action is in-flight.
|
|
if (isBusy.value) {
|
|
return false;
|
|
}
|
|
if (!isCustomerSelected()) {
|
|
return true; // The customer is not selected, so the button can be clicked to open the customer selection popup.
|
|
} else {
|
|
// The customer is selected, so check if the reg_1 is filled.
|
|
return isRegistrationNumberFilled();
|
|
}
|
|
};
|
|
|
|
// Should the button be visible?
|
|
const isVisible = computed(() => {
|
|
/**
|
|
* No popups are open.
|
|
* The step is 1, 2 or 3.
|
|
*/
|
|
return !popups.isSet.value && (step.value === 1 || step.value === 2 || step.value === 3);
|
|
});
|
|
|
|
const isClickCancelled = ref(false);
|
|
const cancelOnClick = () => {
|
|
isClickCancelled.value = true;
|
|
setTimeout(() => {
|
|
isClickCancelled.value = false;
|
|
}, 100); // Reset after 100ms
|
|
};
|
|
|
|
const onLongPress = () => {
|
|
// Cancel the onClick event (since it's a long press)
|
|
cancelOnClick();
|
|
// Check if there's a custom long press handler
|
|
if (props.customLongPress) {
|
|
props.customLongPress();
|
|
return;
|
|
}
|
|
// If no custom long press handler is provided, proceed with the default behavior
|
|
defaultLongPressBehavior();
|
|
};
|
|
|
|
const defaultLongPressBehavior = () => {
|
|
// Select the customer
|
|
popups.select("select_customer");
|
|
};
|
|
|
|
/**
|
|
* Colors:
|
|
* Primary:
|
|
* :buttonClasses="['has-background-primary', 'has-text-black']"
|
|
* Secondary:
|
|
* :buttonClasses="['has-background-primary-dark', 'has-text-black']"
|
|
*/
|
|
</script>
|
|
|
|
<template>
|
|
<LongPressListener @long-press="onLongPress">
|
|
<GenericButton
|
|
@click="onClick"
|
|
:class="{
|
|
white: props.isWhite,
|
|
'has-background-black': props.isDark,
|
|
'is-loading': isBusy,
|
|
...props.buttonClasses.reduce((acc, curr) => ({ ...acc, [curr]: true }), {}),
|
|
}"
|
|
data-testid="pos-mobile-next-step"
|
|
:action-key="props.actionKey"
|
|
:copy-key="props.copyKey"
|
|
class="is-size-6-mobile is-size-5-tablet is-size-4-desktop"
|
|
:disabled="!isRequirementsForClickMet()"
|
|
v-show="isVisible"
|
|
>
|
|
<!-- Default content if no slot is provided -->
|
|
<template v-if="!$slots.default">
|
|
<template v-if="isCustomerSelected()">
|
|
<span class="pos-mobile-cta-content">
|
|
<span class="pos-mobile-cta-label">{{ customer_name }}</span>
|
|
<span class="pos-mobile-cta-value">
|
|
<ContinueArrow style="width: 100%; height: 100%; padding: 6px 12px; gap: 5px" />
|
|
</span>
|
|
</span>
|
|
</template>
|
|
<template v-else>
|
|
<span>{{ selectCustomerString }}</span>
|
|
</template>
|
|
</template>
|
|
<!-- Default slot for custom content -->
|
|
<template v-else>
|
|
<!-- {{ transactionItems.primaryItem.value?.addons?.length }} -->
|
|
<slot></slot>
|
|
</template>
|
|
</GenericButton>
|
|
</LongPressListener>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pos-mobile-cta-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
.pos-mobile-cta-label {
|
|
min-width: 0;
|
|
flex: 1;
|
|
display: -webkit-box;
|
|
text-align: left;
|
|
line-height: 1.2;
|
|
overflow: hidden;
|
|
overflow-wrap: anywhere;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
}
|
|
|
|
.pos-mobile-cta-value {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.has-text-black > :not(.icon) {
|
|
color: black !important;
|
|
}
|
|
|
|
:deep(.pos-mobile-cta-content) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
:deep(.pos-mobile-cta-label) {
|
|
min-width: 0;
|
|
flex: 1;
|
|
display: -webkit-box;
|
|
text-align: left;
|
|
line-height: 1.2;
|
|
overflow: hidden;
|
|
overflow-wrap: anywhere;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
}
|
|
|
|
:deep(.pos-mobile-cta-value) {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
:deep(.pos-mobile-action-content) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
width: 100%;
|
|
min-width: 0;
|
|
min-height: 100%;
|
|
box-sizing: border-box;
|
|
padding-inline: 0.75rem;
|
|
}
|
|
|
|
:deep(.pos-mobile-action-label) {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
text-align: left;
|
|
line-height: 1;
|
|
}
|
|
|
|
:deep(.pos-mobile-action-icon) {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 0 0 auto;
|
|
margin-left: auto;
|
|
min-width: 1.25rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
:deep(.pos-mobile-action-icon i) {
|
|
display: block;
|
|
line-height: 1;
|
|
}
|
|
|
|
:deep(.generic-button__content) {
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
/* Loading state */
|
|
.is-loading {
|
|
pointer-events: none;
|
|
cursor: not-allowed;
|
|
}
|
|
.is-loading > *:not(.icon) {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition: opacity 0.6s ease-in-out;
|
|
}
|
|
/* Loading animation for the button */
|
|
.is-loading::after {
|
|
transition: opacity 0.6s ease-in-out;
|
|
content: "";
|
|
position: absolute;
|
|
top: auto;
|
|
left: 50%;
|
|
width: 1em;
|
|
height: 1em;
|
|
margin-top: -0.5em;
|
|
margin-left: -0.5em;
|
|
/* Set the color to the original text color */
|
|
border: 0.2em solid white;
|
|
border-top-color: transparent;
|
|
border-radius: 50%;
|
|
animation: spin 0.6s linear infinite;
|
|
display: inline-block;
|
|
vertical-align: top;
|
|
border-right-color: transparent;
|
|
border-bottom-color: transparent;
|
|
pointer-events: none;
|
|
}
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
</style>
|