Files
slipmatz-web/app/components/designer/DesignerPreview.vue
Frank John Begornia 4d91925fad feat: add design persistence functionality with Auth0 and Supabase integration
- Implemented `useDesignPersistence` composable for managing design records.
- Enhanced `useSlipmatDesigner` to support loading designs from JSON.
- Created global authentication middleware for route protection.
- Added Supabase client plugin for database interactions.
- Developed API endpoints for fetching, saving, and retrieving designs.
- Introduced utility functions for Auth0 token verification and Supabase client retrieval.
- Updated Nuxt configuration to include Auth0 and Supabase environment variables.
- Added necessary dependencies for Auth0 and Supabase.
- Enhanced TypeScript configuration for improved type support.
2025-11-07 00:01:52 +08:00

155 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
const props = defineProps<{
previewUrl: string | null;
templateLabel: string;
productionPixels: number;
isExporting: boolean;
projectName: string;
isSaving: boolean;
canSave: boolean;
}>();
const emit = defineEmits<{
(e: "download-preview"): void;
(e: "download-production"): void;
(e: "export"): void;
(e: "update:projectName", value: string): void;
(e: "save"): void;
}>();
const viewMode = ref<"flat" | "turntable">("flat");
const isFlat = computed(() => viewMode.value === "flat");
const handleSelectView = (mode: "flat" | "turntable") => {
viewMode.value = mode;
};
const handleExport = () => emit("export");
const handleDownloadPreview = () => emit("download-preview");
const handleDownloadProduction = () => emit("download-production");
const handleProjectNameInput = (event: Event) => {
const target = event.target as HTMLInputElement;
emit("update:projectName", target.value);
};
const handleSave = () => emit("save");
const isSaveDisabled = computed(
() => !props.previewUrl || props.isSaving || !props.canSave
);
</script>
<template>
<section class="rounded-2xl border border-slate-800/60 bg-slate-900/80 p-4 shadow-lg shadow-slate-950/40">
<header class="flex items-center justify-between">
<div>
<h3 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
Output Preview
</h3>
<p class="mt-1 text-sm text-slate-300">
{{ templateLabel }} {{ productionPixels }}×{{ productionPixels }} px
</p>
</div>
<button
type="button"
class="rounded-lg border border-sky-500/60 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-sky-100 transition hover:bg-sky-500/10 disabled:cursor-not-allowed disabled:border-slate-600 disabled:text-slate-500"
:disabled="props.isExporting"
@click="handleExport"
>
{{ props.isExporting ? "Exporting…" : "Generate Files" }}
</button>
</header>
<div class="mt-4 flex gap-2">
<button
type="button"
class="flex-1 rounded-xl border px-3 py-2 text-xs font-semibold uppercase tracking-wide transition"
:class="isFlat ? 'border-sky-500 bg-sky-500/10 text-sky-200' : 'border-slate-800 bg-slate-900 text-slate-400 hover:border-slate-700/80 hover:text-slate-200'"
@click="handleSelectView('flat')"
>
Flat View
</button>
<button
type="button"
class="flex-1 rounded-xl border px-3 py-2 text-xs font-semibold uppercase tracking-wide transition"
:class="!isFlat ? 'border-sky-500 bg-sky-500/10 text-sky-200' : 'border-slate-800 bg-slate-900 text-slate-400 hover:border-slate-700/80 hover:text-slate-200'"
@click="handleSelectView('turntable')"
>
Turntable View
</button>
</div>
<div class="mt-4 aspect-square overflow-hidden rounded-2xl border border-slate-800 bg-slate-950">
<template v-if="props.previewUrl">
<div v-if="isFlat" class="h-full w-full">
<img
:src="props.previewUrl"
alt="Slipmat preview flat"
class="h-full w-full object-cover"
/>
</div>
<div v-else class="relative h-full w-full bg-linear-to-br from-slate-900 via-slate-950 to-black">
<img
src="/turntable-mockup.svg"
alt="Turntable illustration"
class="pointer-events-none h-full w-full object-contain"
/>
<div class="absolute left-[16%] top-[18%] h-[64%] w-[48%] -rotate-2 overflow-hidden rounded-full shadow-xl shadow-black/40">
<div class="absolute inset-0 bg-slate-900/40" />
<img
:src="props.previewUrl"
alt="Slipmat preview turntable"
class="h-full w-full object-cover opacity-95 mix-blend-screen"
/>
</div>
</div>
</template>
<div
v-else
class="flex h-full items-center justify-center text-sm text-slate-500"
>
No preview yetstart designing!
</div>
</div>
<div class="mt-4 grid gap-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-end">
<label class="flex flex-col gap-2">
<span class="text-xs font-semibold uppercase tracking-wide text-slate-400">
Project Name
</span>
<input
type="text"
class="w-full rounded-xl border border-slate-800 bg-slate-900 px-3 py-2 text-sm text-slate-100 shadow-inner shadow-black/20 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
:value="props.projectName"
:disabled="props.isSaving"
maxlength="120"
placeholder="e.g. Midnight Mix Vol. 1"
@input="handleProjectNameInput"
/>
</label>
<button
type="button"
class="h-11 rounded-xl bg-emerald-500 px-6 text-sm font-semibold text-emerald-50 transition hover:bg-emerald-400 disabled:cursor-not-allowed disabled:bg-slate-700/70 disabled:text-slate-400"
:disabled="isSaveDisabled"
@click="handleSave"
>
{{ props.isSaving ? "Saving…" : "Save to Library" }}
</button>
</div>
<div class="mt-4 flex flex-wrap gap-3">
<button
type="button"
class="flex-1 rounded-xl bg-slate-800 px-4 py-3 text-sm font-medium text-slate-100 transition hover:bg-slate-700 disabled:cursor-not-allowed disabled:bg-slate-800/70 disabled:text-slate-500"
:disabled="!props.previewUrl"
@click="handleDownloadPreview"
>
Download Web Preview
</button>
<button
type="button"
class="flex-1 rounded-xl bg-sky-600 px-4 py-3 text-sm font-medium text-white transition hover:bg-sky-500 disabled:cursor-not-allowed disabled:bg-slate-600/70"
:disabled="props.isExporting"
@click="handleDownloadProduction"
>
Download Print-Ready PNG
</button>
</div>
</section>
</template>