Deploy to production
This commit is contained in:
@@ -121,7 +121,7 @@ export const ApiSettingsModal: React.FC<ApiSettingsModalProps> = ({ isOpen, onCl
|
||||
{/* Viz AI (Image-to-Image & LLM) */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="text-xs font-bold text-zinc-500 uppercase tracking-widest flex items-center gap-2">
|
||||
<Sparkles size={14} /> Viz AI & Mockup Generation
|
||||
<Sparkles size={14} /> Titan3d AI & Mockup Generation
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 pl-2 border-l-2 border-zinc-800">
|
||||
|
||||
@@ -3,6 +3,8 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { X, Upload, Settings as SettingsIcon, Image as ImageIcon, Box, Download, AlertCircle, Loader2, Check } from 'lucide-react';
|
||||
import { fal } from '@fal-ai/client';
|
||||
import { cn } from '../lib/utils';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { api } from '../lib/api';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { OrbitControls, Stage } from '@react-three/drei';
|
||||
import { GLTFLoader } from 'three-stdlib';
|
||||
@@ -44,6 +46,7 @@ const ModelPreview = ({ url }: { url: string }) => {
|
||||
};
|
||||
|
||||
export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose, onPushToApp }) => {
|
||||
const { profile, setProfile } = useAuthStore();
|
||||
const [apiProvider, setApiProvider] = useState<'fal' | 'tripo' | 'meshy'>('fal');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [tripoApiKey, setTripoApiKey] = useState('');
|
||||
@@ -146,6 +149,10 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
setError("Please upload an image first.");
|
||||
return;
|
||||
}
|
||||
if (!profile || profile.api_credits <= 0) {
|
||||
setError("Not enough AI Credits. Please contact an administrator.");
|
||||
return;
|
||||
}
|
||||
if (apiProvider === 'fal' && !apiKey) {
|
||||
setError("Fal API Key is required. Please set it in Tools > API Settings.");
|
||||
return;
|
||||
@@ -158,6 +165,17 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
setError("Meshy API Key is required. Please set it in Tools > API Settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
const deductCredit = async () => {
|
||||
if (profile) {
|
||||
try {
|
||||
const { api_credits } = await api.deductCredit();
|
||||
setProfile({ ...profile, api_credits });
|
||||
} catch (e) {
|
||||
console.error("Credit deduction failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
setIsGenerating(true);
|
||||
@@ -202,6 +220,7 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
if (modelUrl) {
|
||||
setGeneratedModelUrl(modelUrl);
|
||||
setProgressMessage("Done!");
|
||||
deductCredit();
|
||||
} else {
|
||||
setError("Failed to extract 3D model from response.");
|
||||
}
|
||||
@@ -268,6 +287,7 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
setGeneratedModelUrl(modelUrl);
|
||||
setProgressMessage("Done!");
|
||||
isDone = true;
|
||||
deductCredit();
|
||||
} else if (status === 'failed' || status === 'cancelled') {
|
||||
throw new Error(`Tripo API Task ${status}`);
|
||||
}
|
||||
@@ -339,6 +359,7 @@ export const ImageTo3DModal: React.FC<ImageTo3DModalProps> = ({ isOpen, onClose,
|
||||
setGeneratedModelUrl(modelUrl);
|
||||
setProgressMessage("Done!");
|
||||
isDone = true;
|
||||
deductCredit();
|
||||
} else if (status === 'FAILED' || status === 'EXPIRED') {
|
||||
throw new Error(`Meshy API Task ${status}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
allowedRoles?: ('user' | 'admin')[];
|
||||
}
|
||||
|
||||
export const ProtectedRoute = ({ allowedRoles = ['user', 'admin'] }: ProtectedRouteProps) => {
|
||||
const { user, profile } = useAuthStore();
|
||||
const location = useLocation();
|
||||
|
||||
if (!user) {
|
||||
// Redirect to login but save the attempted url
|
||||
return <Navigate to="/auth" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
if (profile && !allowedRoles.includes(profile.role)) {
|
||||
// Role not authorized, go to dashboard
|
||||
return <Navigate to="/dashboard" replace />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
@@ -83,7 +83,7 @@ export const RenderModal: React.FC<RenderModalProps> = ({ isOpen, onClose, onRen
|
||||
<div className="flex flex-col gap-8 max-w-2xl">
|
||||
{/* Mode Tabs */}
|
||||
<div className="flex p-1 bg-zinc-950 rounded-lg border border-zinc-800 self-start">
|
||||
{['Still Image', 'Animation', 'VIZ3D XR', 'Configurator', 'CMF'].map((mode) => (
|
||||
{['Still Image', 'Animation', 'Titan3d XR', 'Configurator', 'CMF'].map((mode) => (
|
||||
<button
|
||||
key={mode}
|
||||
className={cn(
|
||||
|
||||
@@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { X, Sparkles, Image as ImageIcon, Loader2, Download, Settings, Wand2, Lightbulb } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { api } from '../lib/api';
|
||||
|
||||
interface VizAiModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -22,6 +24,8 @@ const dataURLtoBlob = (dataurl: string) => {
|
||||
};
|
||||
|
||||
export const VizAiModal: React.FC<VizAiModalProps> = ({ isOpen, onClose, baseImage }) => {
|
||||
const { profile, setProfile } = useAuthStore();
|
||||
|
||||
// API Keys
|
||||
const [stabilityApiKey, setStabilityApiKey] = useState('');
|
||||
const [openAiKey, setOpenAiKey] = useState('');
|
||||
@@ -107,6 +111,11 @@ export const VizAiModal: React.FC<VizAiModalProps> = ({ isOpen, onClose, baseIma
|
||||
setError("Please enter a prompt or use AI Idea.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profile || profile.api_credits <= 0) {
|
||||
setError("Not enough AI Credits. Please contact an administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGeneratingImage(true);
|
||||
setError(null);
|
||||
@@ -137,6 +146,13 @@ export const VizAiModal: React.FC<VizAiModalProps> = ({ isOpen, onClose, baseIma
|
||||
const blob = await response.blob();
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
setGeneratedImage(imageUrl);
|
||||
|
||||
// Deduct credit
|
||||
if (profile) {
|
||||
const { api_credits } = await api.deductCredit();
|
||||
setProfile({ ...profile, api_credits });
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
setError(err.message || "Failed to generate image.");
|
||||
@@ -163,7 +179,7 @@ export const VizAiModal: React.FC<VizAiModalProps> = ({ isOpen, onClose, baseIma
|
||||
<Sparkles size={16} className="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-sm font-bold text-zinc-100">VIZ AI Generator</h2>
|
||||
<h2 className="text-sm font-bold text-zinc-100">Titan3d AI Generator</h2>
|
||||
<p className="text-[10px] text-zinc-400">Generate stunning product mockups directly from your 3D scene</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user