build / build (pull_request) Successful in 1m10s
trackEvent queues events until the deferred Umami script exists (they were dropped when fired in onMounted) and drops them silently after 10 s. The result page sends purchase-completed with revenue/currency once per order (localStorage guard, set only after sending). The plugin honours Do Not Track and loads recorder.js only on /details/* for Umami heatmaps. bun test utils/trackEvent.test.ts: 4 pass. Netcup #115.
254 lines
8.2 KiB
Vue
254 lines
8.2 KiB
Vue
<template>
|
|
<main class="pt-4 lg:container mx-auto px-4">
|
|
<div v-if="displayState === 'loading'">
|
|
<div class="flex items-center justify-center h-[60vh]">
|
|
<div class="text-center">
|
|
<LoadingSpinner />
|
|
<p class="text-2xl mt-8 font-semibold">Bestellung wird geladen...</p>
|
|
<p class="text-lg">Bitte warte einen Moment, während wir deine Bestellung vorbereiten.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="displayState === 'main-content'">
|
|
<Stepper :step="4" />
|
|
|
|
<div class="flex gap-12 relative max-w-screen-lg mx-auto my-8">
|
|
<div v-if="order && order.id" class="flex flex-col gap-8 w-full">
|
|
<ClientOnly>
|
|
<ConfettiExplosion :colors="['#2563eb', '#ec4899', '#16a34a']" />
|
|
</ClientOnly>
|
|
|
|
<div>
|
|
<Heading :level="1">Vielen Dank für deine Bestellung bei MUELLERPRINTS!</Heading>
|
|
|
|
<Heading :level="2">Deine Bestellnummer: {{ order.id }}</Heading>
|
|
</div>
|
|
|
|
<p class="text-lg lg:w-2/3">
|
|
In Kürze erhälst du von uns eine E-Mail mit allen Einzelheiten zu deiner Bestellung. Du kannst sie auch hier herunterladen, sobald sie erstellt
|
|
wurde.
|
|
</p>
|
|
|
|
<div>
|
|
<Button id="download-invoice" :is-pending="!hasReachedMaxFailedRequests && (!isReadyToDownload || isDownloadPending)" @click="downloadInvoice">
|
|
<span v-if="hasReachedMaxFailedRequests">Bald verfügbar</span>
|
|
<span v-else>Rechnung herunterladen</span>
|
|
</Button>
|
|
<div v-if="hasReachedMaxFailedRequests" class="text-sm mt-3">
|
|
Es konnte noch keine Rechnung ermittelt werden. Bitte prüfe deine E-Mails oder kontaktiere uns: order@muellerprints.de
|
|
</div>
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<div class="flex flex-col gap-8">
|
|
<ul class="divide-y divide-gray-300" data-e2e="order-products">
|
|
<li v-for="(position, index) in orderProducts" :key="index" class="py-6 gap-6">
|
|
<div v-if="position.product" class="flex items-center justify-between">
|
|
<img
|
|
v-if="position.product?.images?.images"
|
|
:src="position.product?.images?.images[0]?.formats?.thumbnail?.url"
|
|
:alt="position.product?.name"
|
|
class="w-16 lg:w-24 object-cover"
|
|
/>
|
|
<div v-else class="bg-black opacity-5 w-12 h-12"></div>
|
|
|
|
<a :href="`/details/${position.product?.slug}`" class="p-2 text-xl font-bold flex-grow">{{ position.product?.name }}</a>
|
|
|
|
<span
|
|
data-e2e="cart-products-item-count"
|
|
class="block appearance-none w-16 text-center bg-white border border-gray-300 hover:border-gray-500 px-4 py-2 rounded shadow leading-tight focus:outline-none focus:border-indigo-500 focus:shadow-outline"
|
|
>
|
|
{{ position?.count }}
|
|
</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="displayState === 'terms-warning'">
|
|
<div class="bg-yellow-100 border-l-4 border-yellow-500 p-4 mb-8">
|
|
<p class="text-yellow-700 font-semibold">
|
|
Bitte, akzeptiere die
|
|
<a href="/agb" target="_blank" class="underline">AGB</a>
|
|
und
|
|
<a href="/datenschutz" target="_blank" class="underline">Datenschutzerklärung</a>, um fortzufahren.
|
|
</p>
|
|
<NuxtLink to="/checkout/1" class="text-yellow-700 hover:underline">Zurück zur Kasse</NuxtLink>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="displayState === 'address-warning'">
|
|
<div class="bg-yellow-100 border-l-4 border-yellow-500 p-4 mb-8">
|
|
<p class="text-yellow-700 font-semibold">Bitte, gib deine Lieferadresse an, um fortzufahren.</p>
|
|
<NuxtLink to="/checkout/2" class="text-yellow-700 hover:underline">Zurück zur Kasse</NuxtLink>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="displayState === 'payment-warning'">
|
|
<div class="bg-yellow-100 border-l-4 border-yellow-500 p-4 mb-8">
|
|
<p class="text-yellow-700 font-semibold">Bitte, gib eine Zahlungsart an, um fortzufahren.</p>
|
|
<NuxtLink to="/checkout/3" class="text-yellow-700 hover:underline">Zurück zur Kasse</NuxtLink>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<div class="flex items-center justify-center h-[60vh]">
|
|
<div class="text-center">
|
|
<div class="w-20 h-20"></div>
|
|
<p class="text-2xl mt-8 font-semibold">Bestellung nicht gefunden...</p>
|
|
<p class="text-lg">Leider konnte deine Bestellung nicht unter dieser Adresse gefunden werden.</p>
|
|
<p class="text-lg mt-6">
|
|
Falls das Problem bestehen bleibt, <a href="/kontakt" class="underline">kontaktiere uns</a> bitte. Schicke deine Bestell-ID bitte an
|
|
paperwork@muellerprints.de
|
|
</p>
|
|
<pre class="mt-3 p-2 rounded-sm bg-gray-100">Bestell-ID: {{ uuid }}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Order } from "~/types";
|
|
import { trackEvent } from "~/utils/trackEvent";
|
|
|
|
const route = useRoute();
|
|
const uuid = computed(() => route.params.uuid as string);
|
|
|
|
const shopApi = useShopApi();
|
|
|
|
const order = ref<Order | null>(null);
|
|
const isLoading = ref(true);
|
|
const isDownloadPending = ref(false);
|
|
const failedDownloadRequests = ref(0);
|
|
const hasReachedMaxFailedRequests = ref(false);
|
|
const displayState = ref("loading");
|
|
|
|
const maxFailedRequests = 20;
|
|
|
|
const orderProducts = computed(() => order.value?.cart ?? []);
|
|
const isReadyToDownload = computed(() => !!order.value?.invoice);
|
|
|
|
onMounted(async () => {
|
|
let hasTrackedConversion = false;
|
|
|
|
const fetchAndSetOrder = async () => {
|
|
try {
|
|
order.value = await shopApi.getOrder(uuid.value);
|
|
updateDisplayState();
|
|
|
|
if (!order.value?.acceptedTermsAndConditionsAt || !order.value?.invoiceAddress || !order.value?.paymentAuthorised) {
|
|
console.error("Unauthorised view");
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
// Track order confirmation view (only once)
|
|
if (!hasTrackedConversion) {
|
|
hasTrackedConversion = true;
|
|
trackEvent("order-confirmation-viewed", {
|
|
orderId: order.value.id,
|
|
orderTotal: order.value.total,
|
|
itemCount: order.value.cart?.length ?? 0
|
|
});
|
|
|
|
// Revenue for Umami's revenue report: exactly once per order, even if the
|
|
// customer reloads this page or opens it again from the confirmation mail.
|
|
const purchaseKey = `mp-purchase-tracked-${order.value.uuid}`;
|
|
if (!localStorage.getItem(purchaseKey)) {
|
|
const products = (order.value.cart ?? []).map((item) => item.product?.slug).filter(Boolean);
|
|
trackEvent(
|
|
"purchase-completed",
|
|
{
|
|
revenue: order.value.total,
|
|
currency: "EUR",
|
|
itemCount: (order.value.cart ?? []).reduce((sum, item) => sum + (item.count ?? 1), 0),
|
|
products: products.join(",").slice(0, 400)
|
|
},
|
|
() => localStorage.setItem(purchaseKey, new Date().toISOString())
|
|
);
|
|
}
|
|
}
|
|
|
|
if (order.value?.invoice?.url) {
|
|
console.log("Invoice fetched successfully");
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
failedDownloadRequests.value++;
|
|
|
|
if (failedDownloadRequests.value < maxFailedRequests) {
|
|
console.log("Retrying to fetch invoice", failedDownloadRequests.value);
|
|
setTimeout(fetchAndSetOrder, 3000);
|
|
} else {
|
|
hasReachedMaxFailedRequests.value = true;
|
|
isLoading.value = false;
|
|
}
|
|
} catch (e) {
|
|
console.error("Error fetching order:", e);
|
|
isLoading.value = false;
|
|
updateDisplayState();
|
|
}
|
|
};
|
|
|
|
await fetchAndSetOrder();
|
|
});
|
|
|
|
function updateDisplayState() {
|
|
if (!order.value) {
|
|
displayState.value = "loading";
|
|
return;
|
|
}
|
|
|
|
if (!order.value.id) {
|
|
displayState.value = "not-found-error";
|
|
return;
|
|
}
|
|
|
|
if (!order.value.acceptedTermsAndConditionsAt) {
|
|
displayState.value = "terms-warning";
|
|
return;
|
|
}
|
|
|
|
if (!order.value.invoiceAddress) {
|
|
displayState.value = "address-warning";
|
|
return;
|
|
}
|
|
|
|
if (!order.value.paymentAuthorised) {
|
|
displayState.value = "payment-warning";
|
|
return;
|
|
}
|
|
|
|
displayState.value = "main-content";
|
|
}
|
|
|
|
function downloadInvoice() {
|
|
if (!isReadyToDownload.value) {
|
|
console.error("Invoice not ready to download");
|
|
return;
|
|
}
|
|
try {
|
|
isDownloadPending.value = true;
|
|
if (order.value.invoice) {
|
|
trackEvent("order-invoice-downloaded", {
|
|
orderId: order.value.id
|
|
});
|
|
window.open(order.value.invoice.url, "_blank");
|
|
} else {
|
|
console.error("Invoice URL not available");
|
|
}
|
|
} catch (e) {
|
|
console.error("Error downloading invoice:", e);
|
|
} finally {
|
|
isDownloadPending.value = false;
|
|
}
|
|
}
|
|
|
|
// SEO
|
|
useSeoMeta({
|
|
title: "Bestellbestätigung | MUELLERPRINTS"
|
|
});
|
|
</script>
|