Deploy to production

This commit is contained in:
AI Bot
2026-07-30 12:49:29 +05:30
parent 0d9c9eaadd
commit 07a1e8f37f
30 changed files with 6363 additions and 4887 deletions
+1 -1
View File
@@ -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">
+21
View File
@@ -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}`);
}
+23
View File
@@ -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 />;
};
+1 -1
View File
@@ -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(
+17 -1
View File
@@ -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>