mirror of
https://github.com/hanzoai/dataroom.git
synced 2026-10-30 18:41:07 +00:00
108 files: README, marketing copy, page titles, meta tags, footer text, email templates, .env.example, UI strings. URLs swapped from papermark.com/papermark.io to dataroom.hanzo.ai. Internal identifiers left intact per repo policy: localStorage keys (papermark.email/papermark.name/last_papermark_login — changing them logs users out), type field names (papermarkUserId), internal helper symbols (PAPERMARK_HEADERS, PapermarkSparkle component, isPapermarkUrl helper), internal-only filenames. LLM.md retains 'Upstream: Papermark' attribution line. Per-locale .po translations regenerate from source on the next lingui-extract CI run. DB migrations untouched (append-only history; rewriting them breaks installed schemas). LICENSE stays AGPL-3.0.
334 lines
12 KiB
TypeScript
334 lines
12 KiB
TypeScript
import { useRouter } from "next/router";
|
|
|
|
import { useState } from "react";
|
|
|
|
import { useTeam } from "@/context/team-context";
|
|
import { FileChartPieIcon, FileIcon, PresentationIcon } from "lucide-react";
|
|
import { motion } from "motion/react";
|
|
|
|
import { STAGGER_CHILD_VARIANTS } from "@/lib/constants";
|
|
|
|
import NotionIcon from "@/components/shared/icons/files/notion";
|
|
|
|
const DEAL_TYPE_OPTIONS = [
|
|
{ value: "startup-fundraising", label: "Startup Fundraising" },
|
|
{ value: "fund-management", label: "Fundraising & Reporting" },
|
|
{ value: "mergers-acquisitions", label: "Mergers & Acquisitions" },
|
|
{ value: "financial-operations", label: "Financial Operations" },
|
|
{ value: "real-estate", label: "Real Estate" },
|
|
{ value: "project-management", label: "Project Management" },
|
|
];
|
|
|
|
const DEAL_SIZE_OPTIONS = [
|
|
{ value: "0-500k", label: "$0-500K" },
|
|
{ value: "500k-5m", label: "$500K-5M" },
|
|
{ value: "5m-10m", label: "$5M-10M" },
|
|
{ value: "10m-100m", label: "$10M-100M" },
|
|
{ value: "100m+", label: "$100M+" },
|
|
];
|
|
|
|
export default function Select() {
|
|
const router = useRouter();
|
|
const teamInfo = useTeam();
|
|
const [selectedDoc, setSelectedDoc] = useState<string | null>(null);
|
|
const [dealType, setDealType] = useState<string | null>(null);
|
|
const [dealTypeOther, setDealTypeOther] = useState<string>("");
|
|
const [showOtherInput, setShowOtherInput] = useState(false);
|
|
const [dealSize, setDealSize] = useState<string | null>(null);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
// Determine which survey step to show based on document type
|
|
const needsDealTypeQuestion =
|
|
selectedDoc === "notion" || selectedDoc === "document";
|
|
const needsDealSizeQuestion =
|
|
selectedDoc === "pitchdeck" ||
|
|
selectedDoc === "sales-document" ||
|
|
(needsDealTypeQuestion && dealType && dealType !== "project-management");
|
|
|
|
const showDealTypeOptions = needsDealTypeQuestion && !dealType && !showOtherInput;
|
|
const showDealSizeOptions =
|
|
needsDealSizeQuestion &&
|
|
(dealType || !needsDealTypeQuestion) &&
|
|
!showOtherInput;
|
|
|
|
const handleDocSelect = (docType: string) => {
|
|
setSelectedDoc(docType);
|
|
setDealType(null);
|
|
setDealTypeOther("");
|
|
setShowOtherInput(false);
|
|
setDealSize(null);
|
|
|
|
// Auto-set deal type for specific document types
|
|
if (docType === "pitchdeck") {
|
|
setDealType("startup-fundraising");
|
|
} else if (docType === "sales-document") {
|
|
setDealType("sales");
|
|
}
|
|
};
|
|
|
|
const handleDealTypeSelect = (value: string) => {
|
|
if (isSubmitting) return;
|
|
setDealType(value);
|
|
setShowOtherInput(false);
|
|
if (value === "project-management") {
|
|
setIsSubmitting(true);
|
|
saveSurveyAndProceed(value, null, null);
|
|
}
|
|
};
|
|
|
|
const handleOtherConfirm = () => {
|
|
if (!dealTypeOther.trim()) return;
|
|
setDealType("other");
|
|
setShowOtherInput(false);
|
|
// Show deal size question after confirming "Other"
|
|
};
|
|
|
|
const handleDealSizeSelect = (value: string) => {
|
|
if (isSubmitting) return;
|
|
setDealSize(value);
|
|
setIsSubmitting(true);
|
|
saveSurveyAndProceed(dealType, value, dealTypeOther || null);
|
|
};
|
|
|
|
const saveSurveyAndProceed = async (
|
|
type: string | null,
|
|
size: string | null,
|
|
otherText: string | null,
|
|
) => {
|
|
setIsSubmitting(true);
|
|
|
|
try {
|
|
if (teamInfo?.currentTeam?.id && type) {
|
|
const res = await fetch(
|
|
`/api/teams/${teamInfo.currentTeam.id}/survey`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
dealType: type,
|
|
dealTypeOther: otherText,
|
|
dealSize: size,
|
|
}),
|
|
},
|
|
);
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Survey save failed: ${res.status}`);
|
|
}
|
|
}
|
|
|
|
await router.push({
|
|
pathname: "/welcome",
|
|
query: { type: selectedDoc },
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to save survey:", error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const getDealSizeQuestion = () => {
|
|
if (
|
|
selectedDoc === "pitchdeck" ||
|
|
dealType === "startup-fundraising" ||
|
|
dealType === "fund-management"
|
|
) {
|
|
return "How much are you raising?";
|
|
}
|
|
if (
|
|
dealType === "mergers-acquisitions" ||
|
|
dealType === "real-estate" ||
|
|
dealType === "financial-operations" ||
|
|
dealType === "other"
|
|
) {
|
|
return "What's the deal size?";
|
|
}
|
|
return "What's the typical deal size?";
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
className="z-10 mx-5 flex flex-col items-center space-y-10 text-center sm:mx-auto"
|
|
variants={{
|
|
hidden: { opacity: 0, scale: 0.95 },
|
|
show: {
|
|
opacity: 1,
|
|
scale: 1,
|
|
transition: {
|
|
staggerChildren: 0.2,
|
|
},
|
|
},
|
|
}}
|
|
initial="hidden"
|
|
animate="show"
|
|
exit="hidden"
|
|
transition={{ duration: 0.3, type: "spring" }}
|
|
>
|
|
<motion.div
|
|
variants={STAGGER_CHILD_VARIANTS}
|
|
className="flex flex-col items-center space-y-10 text-center"
|
|
>
|
|
<p className="text-2xl font-bold tracking-tighter text-foreground">
|
|
Hanzo Dataroom
|
|
</p>
|
|
<h1 className="font-display max-w-md text-3xl font-semibold transition-colors sm:text-4xl">
|
|
Which document do you want to share today?
|
|
</h1>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
variants={STAGGER_CHILD_VARIANTS}
|
|
className="grid w-full grid-cols-1 divide-y divide-border rounded-md border border-border text-foreground md:grid-cols-4 md:divide-x"
|
|
>
|
|
<button
|
|
onClick={() => handleDocSelect("pitchdeck")}
|
|
className={`flex min-h-[200px] flex-col items-center justify-center space-y-5 overflow-hidden p-5 transition-colors md:p-10 ${
|
|
selectedDoc === "pitchdeck"
|
|
? "bg-primary/5 ring-2 ring-inset ring-primary"
|
|
: "hover:bg-gray-200 hover:dark:bg-gray-800"
|
|
}`}
|
|
>
|
|
<PresentationIcon className="pointer-events-none h-auto w-12 sm:w-12" />
|
|
<p>Pitchdeck</p>
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => handleDocSelect("sales-document")}
|
|
className={`flex min-h-[200px] flex-col items-center justify-center space-y-5 overflow-hidden p-5 transition-colors md:p-10 ${
|
|
selectedDoc === "sales-document"
|
|
? "bg-primary/5 ring-2 ring-inset ring-primary"
|
|
: "hover:bg-gray-200 hover:dark:bg-gray-800"
|
|
}`}
|
|
>
|
|
<FileChartPieIcon className="pointer-events-none h-auto w-12 sm:w-12" />
|
|
<p>Sales document</p>
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => handleDocSelect("notion")}
|
|
className={`flex min-h-[200px] flex-col items-center justify-center space-y-5 overflow-hidden p-5 transition-colors md:p-10 ${
|
|
selectedDoc === "notion"
|
|
? "bg-primary/5 ring-2 ring-inset ring-primary"
|
|
: "hover:bg-gray-200 hover:dark:bg-gray-800"
|
|
}`}
|
|
>
|
|
<NotionIcon className="pointer-events-none h-auto w-12 sm:w-12" />
|
|
<p>Notion Page</p>
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => handleDocSelect("document")}
|
|
className={`flex min-h-[200px] flex-col items-center justify-center space-y-5 overflow-hidden p-5 transition-colors md:p-10 ${
|
|
selectedDoc === "document"
|
|
? "bg-primary/5 ring-2 ring-inset ring-primary"
|
|
: "hover:bg-gray-200 hover:dark:bg-gray-800"
|
|
}`}
|
|
>
|
|
<FileIcon className="pointer-events-none h-auto w-12 sm:w-12" />
|
|
<p>Another document</p>
|
|
</button>
|
|
</motion.div>
|
|
|
|
{/* Inline Survey Questions */}
|
|
{selectedDoc && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="w-full max-w-xl space-y-4"
|
|
>
|
|
{/* Deal Type Question (for notion/document) */}
|
|
{showDealTypeOptions && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm font-medium text-muted-foreground">
|
|
What do you use Hanzo Dataroom for?
|
|
</p>
|
|
<div className="flex flex-wrap justify-center gap-2">
|
|
{DEAL_TYPE_OPTIONS.map((option) => (
|
|
<button
|
|
key={option.value}
|
|
onClick={() => handleDealTypeSelect(option.value)}
|
|
disabled={isSubmitting}
|
|
className="rounded-full border border-border px-4 py-2 text-sm transition-all hover:border-primary hover:bg-primary/5"
|
|
>
|
|
{option.label}
|
|
</button>
|
|
))}
|
|
<button
|
|
onClick={() => setShowOtherInput(true)}
|
|
disabled={isSubmitting}
|
|
className="rounded-full border border-border px-4 py-2 text-sm transition-all hover:border-primary hover:bg-primary/5"
|
|
>
|
|
Other
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Other Input Field - inline */}
|
|
{showOtherInput && !dealType && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm font-medium text-muted-foreground">
|
|
What do you use Hanzo Dataroom for?
|
|
</p>
|
|
<div className="flex items-center justify-center gap-2">
|
|
<input
|
|
type="text"
|
|
value={dealTypeOther}
|
|
onChange={(e) => setDealTypeOther(e.target.value)}
|
|
placeholder="Please specify..."
|
|
className="max-w-xs rounded-full border border-border bg-background px-4 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
|
|
autoFocus
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && dealTypeOther.trim()) {
|
|
handleOtherConfirm();
|
|
}
|
|
}}
|
|
/>
|
|
<button
|
|
onClick={handleOtherConfirm}
|
|
disabled={!dealTypeOther.trim() || isSubmitting}
|
|
className="rounded-full border border-primary bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all hover:bg-primary/90 disabled:opacity-50"
|
|
>
|
|
→
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Deal Size Question */}
|
|
{showDealSizeOptions && !showDealTypeOptions && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm font-medium text-muted-foreground">
|
|
{getDealSizeQuestion()}
|
|
</p>
|
|
<div className="flex flex-wrap justify-center gap-2">
|
|
{DEAL_SIZE_OPTIONS.map((option) => (
|
|
<button
|
|
key={option.value}
|
|
onClick={() => handleDealSizeSelect(option.value)}
|
|
disabled={isSubmitting}
|
|
className="rounded-full border border-border px-4 py-2 text-sm transition-all hover:border-primary hover:bg-primary/5"
|
|
>
|
|
{option.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Skip option */}
|
|
<button
|
|
onClick={() => saveSurveyAndProceed(dealType, null, dealTypeOther || null)}
|
|
disabled={isSubmitting}
|
|
aria-disabled={isSubmitting}
|
|
className="text-xs text-muted-foreground underline-offset-4 hover:underline disabled:opacity-50 disabled:pointer-events-none"
|
|
>
|
|
Skip
|
|
</button>
|
|
</motion.div>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
}
|