feat: extract shop from mp/shop — initial libreshop/shop
Some checks failed
Build and publish / build (push) Failing after 19s
Some checks failed
Build and publish / build (push) Failing after 19s
Source moved verbatim from mp/shop/ on 2026-04-29; mp was the first concrete adapter consuming the libreshop toolkit. Builds and publishes git.librete.ch/libreshop/shop on every main / v* push via the standard .gitea/workflows/build.yml shared across libreshop components.
This commit is contained in:
236
pages/checkout/result/[uuid].vue
Normal file
236
pages/checkout/result/[uuid].vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<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
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user