202 lines
5.9 KiB
Vue
202 lines
5.9 KiB
Vue
<script setup lang="ts">
|
|
import DesignerCanvas from "~/components/designer/DesignerCanvas.vue";
|
|
import DesignerPreview from "~/components/designer/DesignerPreview.vue";
|
|
import DesignerToolbar from "~/components/designer/DesignerToolbar.vue";
|
|
import TemplatePicker from "~/components/designer/TemplatePicker.vue";
|
|
|
|
import { useSlipmatDesigner } from "~/composables/useSlipmatDesigner";
|
|
import type { ExportedDesign } from "~/composables/useSlipmatDesigner";
|
|
|
|
const {
|
|
templates,
|
|
selectedTemplate,
|
|
selectTemplate,
|
|
displaySize,
|
|
templateLabel,
|
|
productionPixelSize,
|
|
previewUrl,
|
|
registerCanvas,
|
|
unregisterCanvas,
|
|
addTextbox,
|
|
addShape,
|
|
addImageFromFile,
|
|
clearDesign,
|
|
downloadPreview,
|
|
downloadProduction,
|
|
exportDesign,
|
|
isExporting,
|
|
activeFillColor,
|
|
activeStrokeColor,
|
|
canStyleSelection,
|
|
setActiveFillColor,
|
|
setActiveStrokeColor,
|
|
zoomLevel,
|
|
minZoom,
|
|
maxZoom,
|
|
setZoom,
|
|
zoomIn,
|
|
zoomOut,
|
|
resetZoom,
|
|
} = useSlipmatDesigner();
|
|
|
|
const DESIGN_PRICE_USD = 39.99;
|
|
|
|
const { user } = useAuth();
|
|
const loginModal = useLoginModal();
|
|
|
|
const isCheckoutPending = ref(false);
|
|
const checkoutError = ref<string | null>(null);
|
|
const lastExportedDesign = ref<ExportedDesign | null>(null);
|
|
|
|
const handleTemplateSelect = (templateId: string) => {
|
|
selectTemplate(templateId);
|
|
};
|
|
|
|
const handleExport = async () => {
|
|
const result = await exportDesign();
|
|
if (result) {
|
|
lastExportedDesign.value = result;
|
|
}
|
|
};
|
|
|
|
const handleCheckout = async () => {
|
|
if (!process.client) {
|
|
return;
|
|
}
|
|
|
|
if (!user.value) {
|
|
loginModal.value = true;
|
|
return;
|
|
}
|
|
|
|
checkoutError.value = null;
|
|
isCheckoutPending.value = true;
|
|
|
|
try {
|
|
let exportResult = await exportDesign();
|
|
if (!exportResult) {
|
|
exportResult = lastExportedDesign.value;
|
|
}
|
|
if (!exportResult) {
|
|
throw new Error("Unable to export the current design. Please try again.");
|
|
}
|
|
lastExportedDesign.value = exportResult;
|
|
|
|
const designId =
|
|
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
? crypto.randomUUID()
|
|
: `design-${Date.now()}`;
|
|
|
|
const successUrlTemplate = `${window.location.origin}/checkout/success?session_id={CHECKOUT_SESSION_ID}`;
|
|
const cancelUrl = window.location.href;
|
|
|
|
const session = await $fetch<{ id: string; url?: string | null }>("/api/checkout.session", {
|
|
method: "POST",
|
|
body: {
|
|
designId,
|
|
templateId: exportResult.templateId,
|
|
designName: templateLabel.value,
|
|
amount: DESIGN_PRICE_USD,
|
|
currency: "usd",
|
|
successUrl: successUrlTemplate,
|
|
cancelUrl,
|
|
customerEmail: user.value?.email,
|
|
},
|
|
});
|
|
|
|
if (!session?.id) {
|
|
throw new Error("Stripe session could not be created.");
|
|
}
|
|
|
|
if (session.url) {
|
|
window.location.href = session.url;
|
|
return;
|
|
}
|
|
|
|
const fallbackRedirect = successUrlTemplate.replace("{CHECKOUT_SESSION_ID}", session.id);
|
|
window.location.href = fallbackRedirect;
|
|
} catch (err: any) {
|
|
console.error("Checkout failed", err);
|
|
checkoutError.value = err?.message ?? "Unable to start checkout. Please try again.";
|
|
} finally {
|
|
isCheckoutPending.value = false;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<main class="min-h-screen bg-slate-950 pb-16 text-slate-100">
|
|
<AppNavbar />
|
|
<div class="mx-auto max-w-6xl px-4 pt-12 sm:px-6 lg:px-8">
|
|
<header class="space-y-3">
|
|
<p class="text-sm uppercase tracking-[0.35em] text-sky-400">
|
|
Slipmatz Designer
|
|
</p>
|
|
<h1 class="text-3xl font-bold text-white sm:text-4xl">
|
|
Craft custom slipmats ready for the pressing plant.
|
|
</h1>
|
|
</header>
|
|
|
|
<section class="mt-10 grid gap-8 lg:grid-cols-[320px_minmax(0,1fr)]">
|
|
<div class="space-y-6">
|
|
<TemplatePicker
|
|
:templates="templates"
|
|
:selected-template-id="selectedTemplate.id"
|
|
@select="handleTemplateSelect"
|
|
/>
|
|
|
|
<DesignerToolbar
|
|
:on-add-text="addTextbox"
|
|
:on-add-circle="() => addShape('circle')"
|
|
:on-add-rectangle="() => addShape('rect')"
|
|
:on-clear="clearDesign"
|
|
:on-import-image="addImageFromFile"
|
|
:on-fill-change="setActiveFillColor"
|
|
:on-stroke-change="setActiveStrokeColor"
|
|
:active-fill="activeFillColor"
|
|
:active-stroke="activeStrokeColor"
|
|
:can-style-selection="canStyleSelection"
|
|
:zoom="zoomLevel"
|
|
:min-zoom="minZoom"
|
|
:max-zoom="maxZoom"
|
|
:on-zoom-change="setZoom"
|
|
:on-zoom-in="zoomIn"
|
|
:on-zoom-out="zoomOut"
|
|
:on-zoom-reset="resetZoom"
|
|
/>
|
|
|
|
<DesignerPreview
|
|
:preview-url="previewUrl"
|
|
:template-label="templateLabel"
|
|
:production-pixels="productionPixelSize"
|
|
:is-exporting="isExporting"
|
|
:is-checkout-pending="isCheckoutPending"
|
|
:checkout-price="DESIGN_PRICE_USD"
|
|
:checkout-error="checkoutError"
|
|
@export="handleExport"
|
|
@download-preview="downloadPreview"
|
|
@download-production="downloadProduction"
|
|
@checkout="handleCheckout"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-6">
|
|
<div class="rounded-3xl border border-slate-800/60 bg-linear-to-br from-slate-900 via-slate-950 to-black p-6 shadow-2xl shadow-slate-950/60">
|
|
<DesignerCanvas
|
|
:size="displaySize"
|
|
:background-color="selectedTemplate.backgroundColor"
|
|
:register-canvas="registerCanvas"
|
|
:unregister-canvas="unregisterCanvas"
|
|
/>
|
|
<p class="mt-4 text-sm text-slate-400">
|
|
Safe zone and bleed guides update automatically when you switch
|
|
templates. Use the toolbar to layer text, shapes, and imagery inside the
|
|
circular boundary.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</template>
|