- 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.
154 lines
3.7 KiB
TypeScript
154 lines
3.7 KiB
TypeScript
import { computed, ref } from "vue";
|
|
import { useAuth0 } from "@auth0/auth0-vue";
|
|
import { useNuxtApp, useRuntimeConfig } from "nuxt/app";
|
|
import type { $Fetch } from "ofetch";
|
|
|
|
import type { ExportedDesign } from "./useSlipmatDesigner";
|
|
|
|
type DesignRecord = {
|
|
id: string;
|
|
name: string;
|
|
template_id: string;
|
|
preview_url: string | null;
|
|
preview_path?: string | null;
|
|
design_json?: unknown;
|
|
notes?: string | null;
|
|
created_at?: string | null;
|
|
updated_at?: string | null;
|
|
};
|
|
|
|
export interface SavedDesign {
|
|
id: string;
|
|
name: string;
|
|
templateId: string;
|
|
previewUrl: string | null;
|
|
notes?: string | null;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
designJson?: unknown;
|
|
}
|
|
|
|
type SaveOptions = {
|
|
id?: string;
|
|
name: string;
|
|
design: ExportedDesign;
|
|
notes?: string;
|
|
};
|
|
|
|
const mapDesignRecord = (record: DesignRecord): SavedDesign => ({
|
|
id: record.id,
|
|
name: record.name,
|
|
templateId: record.template_id,
|
|
previewUrl: record.preview_url ?? null,
|
|
notes: record.notes ?? undefined,
|
|
createdAt: record.created_at ?? null,
|
|
updatedAt: record.updated_at ?? null,
|
|
designJson: record.design_json,
|
|
});
|
|
|
|
export const useDesignPersistence = () => {
|
|
const runtime = useRuntimeConfig();
|
|
const audience = runtime.public.auth0?.audience;
|
|
|
|
const auth0 = process.client ? useAuth0() : null;
|
|
const nuxtApp = useNuxtApp();
|
|
const fetcher = nuxtApp.$fetch as $Fetch;
|
|
|
|
const designs = ref<SavedDesign[]>([]);
|
|
const isLoading = ref(false);
|
|
const isSaving = ref(false);
|
|
const lastError = ref<string | null>(null);
|
|
|
|
const isAuthenticated = computed(() => auth0?.isAuthenticated.value ?? false);
|
|
|
|
const acquireToken = async (): Promise<string> => {
|
|
if (!auth0) {
|
|
throw new Error("Auth0 client is not available.");
|
|
}
|
|
|
|
return auth0.getAccessTokenSilently({
|
|
authorizationParams: audience
|
|
? {
|
|
audience,
|
|
}
|
|
: undefined,
|
|
});
|
|
};
|
|
|
|
const fetchDesigns = async () => {
|
|
if (!auth0 || !isAuthenticated.value) {
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
lastError.value = null;
|
|
|
|
try {
|
|
const token = await acquireToken();
|
|
const response = await fetcher<DesignRecord[]>("/api/designs", {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
designs.value = Array.isArray(response)
|
|
? response.map(mapDesignRecord)
|
|
: [];
|
|
} catch (error) {
|
|
lastError.value = error instanceof Error ? error.message : String(error);
|
|
throw error;
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const saveDesign = async ({ id, name, design, notes }: SaveOptions) => {
|
|
if (!auth0 || !isAuthenticated.value) {
|
|
throw new Error("User must be authenticated before saving a design.");
|
|
}
|
|
|
|
isSaving.value = true;
|
|
lastError.value = null;
|
|
|
|
try {
|
|
const token = await acquireToken();
|
|
const payload = {
|
|
id,
|
|
name,
|
|
templateId: design.templateId,
|
|
previewDataUrl: design.previewUrl,
|
|
productionDataUrl: design.productionUrl,
|
|
designJson: design.canvasJson,
|
|
notes,
|
|
};
|
|
|
|
const response = await fetcher<DesignRecord>("/api/designs", {
|
|
method: "POST",
|
|
body: payload,
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
const saved = mapDesignRecord(response);
|
|
const next = designs.value.filter((item) => item.id !== saved.id);
|
|
designs.value = [saved, ...next];
|
|
return saved;
|
|
} catch (error) {
|
|
lastError.value = error instanceof Error ? error.message : String(error);
|
|
throw error;
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
return {
|
|
designs,
|
|
isLoading,
|
|
isSaving,
|
|
lastError,
|
|
isAuthenticated,
|
|
fetchDesigns,
|
|
saveDesign,
|
|
};
|
|
};
|