Replace Firebase Storage with MinIO and add user account features
- Storage Integration: * Remove Firebase Storage dependency and useFirebaseStorage composable * Implement direct MinIO uploads via POST /storage/upload with multipart/form-data * Upload canvas JSON, preview PNG, and production PNG as separate objects * Store public URLs and metadata in design records - Authentication & Registration: * Add email/password registration page with validation * Integrate backend user session via /auth/login endpoint * Store backendUser.id as ownerId in design records * Auto-sync backend session on Firebase auth state changes - User Account Pages: * Create profile page showing user details and backend session info * Create orders page with transaction history filtered by customerEmail * Add server proxy /api/orders to forward GET /transactions queries - Navigation Improvements: * Replace inline auth buttons with avatar dropdown menu * Add Profile, Orders, and Logout options to dropdown * Implement outside-click and route-change handlers for dropdown * Display user initials in avatar badge - API Updates: * Update transactions endpoint to accept amount as string * Format amount with .toFixed(2) in checkout success flow * Query orders by customerEmail instead of ownerId for consistency
This commit is contained in:
@@ -13,15 +13,6 @@ export interface SlipmatTemplate {
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export interface ExportedDesign {
|
||||
previewUrl: string;
|
||||
previewBlob: Blob;
|
||||
productionUrl: string;
|
||||
productionBlob: Blob;
|
||||
templateId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
const DISPLAY_SIZE = 720;
|
||||
const PREVIEW_SIZE = 1024;
|
||||
const MIN_ZOOM = 0.5;
|
||||
@@ -59,6 +50,7 @@ const TEMPLATE_PRESETS: SlipmatTemplate[] = [
|
||||
];
|
||||
|
||||
type FabricCanvas = FabricNamespace.Canvas;
|
||||
type FabricSerializedCanvas = ReturnType<FabricCanvas["toJSON"]>;
|
||||
type FabricCircle = FabricNamespace.Circle;
|
||||
type FabricRect = FabricNamespace.Rect;
|
||||
type FabricTextbox = FabricNamespace.Textbox;
|
||||
@@ -69,6 +61,16 @@ type CanvasReadyPayload = {
|
||||
fabric: FabricModule;
|
||||
};
|
||||
|
||||
export interface ExportedDesign {
|
||||
previewUrl: string;
|
||||
previewBlob: Blob;
|
||||
productionUrl: string;
|
||||
productionBlob: Blob;
|
||||
templateId: string;
|
||||
createdAt: string;
|
||||
canvasJson: FabricSerializedCanvas;
|
||||
}
|
||||
|
||||
const FALLBACK_TEMPLATE: SlipmatTemplate = TEMPLATE_PRESETS[0] ?? {
|
||||
id: "custom",
|
||||
name: "Custom",
|
||||
@@ -605,6 +607,77 @@ export const useSlipmatDesigner = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const waitForCanvasReady = async (): Promise<FabricCanvas> => {
|
||||
if (canvas.value) {
|
||||
return canvas.value;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const stop = watch(
|
||||
canvas,
|
||||
(value) => {
|
||||
if (value) {
|
||||
stop();
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
});
|
||||
|
||||
if (!canvas.value) {
|
||||
throw new Error("Canvas not ready");
|
||||
}
|
||||
|
||||
return canvas.value;
|
||||
};
|
||||
|
||||
const loadDesign = async (payload: {
|
||||
templateId?: string | null;
|
||||
canvasJson: string | FabricSerializedCanvas;
|
||||
previewUrl?: string | null;
|
||||
}) => {
|
||||
if (payload.templateId) {
|
||||
selectTemplate(payload.templateId);
|
||||
}
|
||||
|
||||
const currentCanvas = await waitForCanvasReady();
|
||||
if (!fabricApi.value) {
|
||||
throw new Error("Fabric API not ready");
|
||||
}
|
||||
|
||||
const parsedJson =
|
||||
typeof payload.canvasJson === "string"
|
||||
? (JSON.parse(payload.canvasJson) as FabricSerializedCanvas)
|
||||
: payload.canvasJson;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
currentCanvas.loadFromJSON(parsedJson, () => {
|
||||
maintainStaticLayerOrder();
|
||||
updateSelectedStyleState();
|
||||
currentCanvas.renderAll();
|
||||
schedulePreviewRefresh();
|
||||
resolve();
|
||||
});
|
||||
}).catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
// Reset cached assets; caller can provide preview if available.
|
||||
previewBlob.value = null;
|
||||
productionBlob.value = null;
|
||||
if (productionObjectUrl.value) {
|
||||
URL.revokeObjectURL(productionObjectUrl.value);
|
||||
productionObjectUrl.value = null;
|
||||
}
|
||||
|
||||
if (payload.previewUrl) {
|
||||
previewUrl.value = payload.previewUrl;
|
||||
} else {
|
||||
await refreshPreview();
|
||||
}
|
||||
};
|
||||
|
||||
const exportDesign = async (): Promise<ExportedDesign | null> => {
|
||||
const currentCanvas = canvas.value;
|
||||
if (!currentCanvas) {
|
||||
@@ -629,6 +702,8 @@ export const useSlipmatDesigner = () => {
|
||||
});
|
||||
const productionDataBlob = await dataUrlToBlob(productionDataUrl);
|
||||
|
||||
const canvasJson = currentCanvas.toJSON();
|
||||
|
||||
previewUrl.value = previewDataUrl;
|
||||
previewBlob.value = previewDataBlob;
|
||||
productionBlob.value = productionDataBlob;
|
||||
@@ -645,6 +720,7 @@ export const useSlipmatDesigner = () => {
|
||||
productionBlob: productionDataBlob,
|
||||
templateId: selectedTemplate.value.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
canvasJson,
|
||||
};
|
||||
} finally {
|
||||
isExporting.value = false;
|
||||
@@ -689,6 +765,7 @@ export const useSlipmatDesigner = () => {
|
||||
templates,
|
||||
selectedTemplate,
|
||||
selectTemplate,
|
||||
loadDesign,
|
||||
displaySize,
|
||||
productionPixelSize,
|
||||
templateLabel,
|
||||
@@ -697,9 +774,9 @@ export const useSlipmatDesigner = () => {
|
||||
productionBlob,
|
||||
productionObjectUrl,
|
||||
isExporting,
|
||||
activeFillColor,
|
||||
activeStrokeColor,
|
||||
canStyleSelection,
|
||||
activeFillColor,
|
||||
activeStrokeColor,
|
||||
canStyleSelection,
|
||||
zoomLevel,
|
||||
zoomPercent,
|
||||
minZoom: MIN_ZOOM,
|
||||
@@ -709,8 +786,8 @@ export const useSlipmatDesigner = () => {
|
||||
addTextbox,
|
||||
addShape,
|
||||
addImageFromFile,
|
||||
setActiveFillColor,
|
||||
setActiveStrokeColor,
|
||||
setActiveFillColor,
|
||||
setActiveStrokeColor,
|
||||
setZoom,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
|
||||
Reference in New Issue
Block a user