AI / src /components /InteractiveDocumentCard.tsx
Aura-Studio's picture
Update src/components/InteractiveDocumentCard.tsx
717b6c1 verified
Raw History Blame Contribute Delete
8.23 kB
import React, { useState } from "react";
import { FileText, FileSpreadsheet, Presentation, Download, Sparkles, Check, AlertCircle, Lock } from "lucide-react";
import type { DocType } from "../utils/documentGenerator";
interface InteractiveDocumentCardProps {
docData: {
type: DocType;
title: string;
subtitle?: string;
headers?: string[];
rows?: Array<Array<string | number | boolean>>;
sections?: Array<{ heading?: string; body: string | string[] }>;
slides?: Array<{ title: string; bullets?: string[]; content?: string; keyTakeaway?: string }>;
};
currentUserTier?: "guest" | "free" | "pro" | "tiniagents";
onUpgradeClick?: () => void;
}
export const InteractiveDocumentCard: React.FC<InteractiveDocumentCardProps> = ({
docData,
currentUserTier = "free",
onUpgradeClick
}) => {
const [isGenerating, setIsGenerating] = useState(false);
const [downloadSuccess, setDownloadSuccess] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const docType = docData.type || "docx";
const title = docData.title || "Generated Document";
const slidesCount = docData.slides?.length || 1;
// Document creation is currently available to every tier.
const perm: { allowed: boolean; maxSlidesAllowed: number; reason?: string } = {
allowed: true,
maxSlidesAllowed: 20
};
const getDocIcon = () => {
switch (docType) {
case "xlsx":
return <FileSpreadsheet className="h-6 w-6 text-emerald-500" />;
case "pdf":
return <FileText className="h-6 w-6 text-rose-500" />;
case "pptx":
return <Presentation className="h-6 w-6 text-amber-500" />;
case "docx":
default:
return <FileText className="h-6 w-6 text-indigo-500" />;
}
};
const getDocBadge = () => {
switch (docType) {
case "xlsx":
return { label: "Excel Spreadsheet (.xlsx)", color: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/30" };
case "pdf":
return { label: "PDF Document (.pdf)", color: "bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/30" };
case "pptx":
return { label: `PowerPoint (${slidesCount} Slides)`, color: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/30" };
case "docx":
default:
return { label: "Word Document (.docx)", color: "bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/30" };
}
};
const badge = getDocBadge();
const handleDownload = async () => {
setErrorMsg(null);
if (!perm.allowed) {
setErrorMsg(perm.reason || "This document format is restricted for your active subscription tier.");
return;
}
try {
setIsGenerating(true);
const {
createWordDocument,
createExcelSpreadsheet,
createPdfDocument,
createPptxPresentation,
triggerFileDownload
} = await import("../utils/documentGenerator");
let blob: Blob;
let defaultFileName = `${title.toLowerCase().replace(/[^a-z0-9]/g, "_")}.${docType}`;
if (docType === "docx") {
blob = await createWordDocument({
title,
subtitle: docData.subtitle,
sections: docData.sections || [{ heading: "Summary", body: title }]
});
} else if (docType === "xlsx") {
blob = createExcelSpreadsheet({
fileName: title,
headers: docData.headers || ["Item", "Description", "Value"],
rows: docData.rows || []
});
} else if (docType === "pdf") {
blob = createPdfDocument({
title,
subtitle: docData.subtitle,
content: (docData.sections || []).map((s) => ({
heading: s.heading,
text: Array.isArray(s.body) ? s.body.join("\n\n") : s.body
}))
});
} else if (docType === "pptx") {
const maxSlides = perm.maxSlidesAllowed || 10;
blob = await createPptxPresentation(
{
title,
subtitle: docData.subtitle,
slides: docData.slides || []
},
maxSlides
);
} else {
throw new Error("Unsupported document type");
}
triggerFileDownload(blob, defaultFileName);
setDownloadSuccess(true);
setTimeout(() => setDownloadSuccess(false), 4000);
// Track document generation usage in background
try {
const currentUsername = (window as any)?.__AURA_CURRENT_USER__?.username || localStorage.getItem("aura_user_name") || "";
fetch("/api/subscriptions/track-usage", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: currentUsername,
type: "documents",
count: 1
})
}).catch(() => {});
} catch (e) {}
} catch (err: any) {
console.error("Failed to generate document:", err);
setErrorMsg(err.message || "An error occurred while generating the document.");
} finally {
setIsGenerating(false);
}
};
return (
<div className="my-4 rounded-2xl border border-slate-200 dark:border-slate-800 bg-gradient-to-br from-white via-slate-50/50 to-indigo-50/20 dark:from-slate-900 dark:via-slate-900/80 dark:to-slate-950 p-4 shadow-md transition-all hover:shadow-lg">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<div className="flex items-center gap-3 min-w-0">
<div className="h-11 w-11 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200/80 dark:border-slate-700/80 flex items-center justify-center shrink-0 shadow-xs">
{getDocIcon()}
</div>
<div className="min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-md border ${badge.color}`}>
{badge.label}
</span>
<span className="text-[10px] font-bold text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20">
100% FREE
</span>
</div>
<h4 className="text-sm font-bold text-slate-900 dark:text-white truncate mt-1">
{title}
</h4>
{docData.subtitle && (
<p className="text-xs text-slate-500 dark:text-slate-400 truncate">
{docData.subtitle}
</p>
)}
</div>
</div>
{/* Action Button */}
<div className="shrink-0 w-full sm:w-auto">
<button
onClick={handleDownload}
disabled={isGenerating}
className={`w-full sm:w-auto h-10 px-4 rounded-xl font-bold text-xs flex items-center justify-center gap-2 transition-all cursor-pointer ${
downloadSuccess
? "bg-emerald-600 text-white shadow-md shadow-emerald-600/20"
: "bg-indigo-600 hover:bg-indigo-500 text-white shadow-md shadow-indigo-600/20 active:scale-95"
}`}
>
{isGenerating ? (
<>
<Sparkles className="h-4 w-4 animate-spin text-amber-300" />
<span>Compiling Document...</span>
</>
) : downloadSuccess ? (
<>
<Check className="h-4 w-4 text-emerald-200" />
<span>Downloaded!</span>
</>
) : (
<>
<Download className="h-4 w-4" />
<span>Download {docType.toUpperCase()}</span>
</>
)}
</button>
</div>
</div>
{/* Error / Restriction Warning */}
{errorMsg && (
<div className="mt-3 p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/30 text-amber-700 dark:text-amber-300 text-xs flex items-start gap-2">
<AlertCircle className="h-4 w-4 text-amber-500 shrink-0 mt-0.5" />
<div className="flex-1">
<span>{errorMsg}</span>
</div>
</div>
)}
</div>
);
};