Skip to content
103 changes: 76 additions & 27 deletions components/links/link-sheet/agreement-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { useTeam } from "@/context/team-context";
import { toast } from "sonner";
import { mutate } from "swr";

import {
DocumentData,
createAgreementDocument,
} from "@/lib/documents/create-document";
import { putFile } from "@/lib/files/put-file";
import { getSupportedContentType } from "@/lib/utils/get-content-type";

import DocumentUpload from "@/components/document-upload";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand All @@ -24,33 +31,55 @@ import {
SheetTitle,
} from "@/components/ui/sheet";

import {
DocumentData,
createAgreementDocument,
} from "@/lib/documents/create-document";
import { putFile } from "@/lib/files/put-file";
import { getSupportedContentType } from "@/lib/utils/get-content-type";

import LinkItem from "../link-item";

export default function AgreementSheet({
defaultData,
isOpen,
setIsOpen,
isOnlyView = false,
onClose,
}: {
defaultData?: { name: string; link: string; requireName: boolean } | null;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
isOnlyView?: boolean;
onClose?: () => void;
}) {
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const [data, setData] = useState({ name: "", link: "", requireName: true });
const [isLoading, setIsLoading] = useState<boolean>(false);
const [currentFile, setCurrentFile] = useState<File | null>(null);
const [currentLink, setCurrentLink] = useState<string | null>(null);

useEffect(() => {
if (defaultData) {
setData({
name: defaultData?.name || "",
link: defaultData?.link || "",
requireName: defaultData?.requireName || true,
});
}
}, [defaultData]);

const handleClose = (open: boolean) => {
setIsOpen(open);
setData({ name: "", link: "", requireName: true });
setCurrentFile(null);
setIsLoading(false);
if (onClose) {
onClose();
}
};

const handleBrowserUpload = async () => {
// event.preventDefault();
// event.stopPropagation();

if (isOnlyView) {
handleClose(false);
toast.error("Cannot upload file in view mode!");
return;
}
// Check if the file is chosen
if (!currentFile) {
toast.error("Please select a file to upload.");
Expand Down Expand Up @@ -112,6 +141,11 @@ export default function AgreementSheet({
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
e.stopPropagation();
if (isOnlyView) {
handleClose(false);
toast.error("Agreement cannot be created in view mode");
return;
}

setIsLoading(true);

Expand Down Expand Up @@ -151,13 +185,16 @@ export default function AgreementSheet({
}, [currentFile]);

return (
<Sheet open={isOpen} onOpenChange={setIsOpen}>
<Sheet open={isOpen} onOpenChange={handleClose}>
<SheetContent className="flex h-full w-[85%] flex-col justify-between bg-background px-4 text-foreground sm:w-[500px] md:px-5">
<SheetHeader className="text-start">
<SheetTitle>Create a new agreement</SheetTitle>
<SheetTitle>
{isOnlyView ? "View Agreement" : "Create a new agreement"}
</SheetTitle>
<SheetDescription>
An agreement is a special document that visitors must accept before
accessing your link. You can create a new agreement here.
{isOnlyView
? "View the details of this agreement."
: "An agreement is a special document that visitors must accept before accessing your link. You can create a new agreement here."}
</SheetDescription>
</SheetHeader>

Expand All @@ -182,6 +219,7 @@ export default function AgreementSheet({
name: e.target.value,
})
}
disabled={isOnlyView}
/>
</div>

Expand All @@ -192,6 +230,7 @@ export default function AgreementSheet({
action={() =>
setData({ ...data, requireName: !data.requireName })
}
isAllowed={!isOnlyView}
/>
</div>

Expand Down Expand Up @@ -220,28 +259,38 @@ export default function AgreementSheet({
"Please enter a valid URL starting with https://",
)
}
disabled={isOnlyView}
/>
</div>

<div className="space-y-12">
<div className="space-y-2 pb-6">
<Label>Or upload an agreement</Label>
<div className="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<DocumentUpload
currentFile={currentFile}
setCurrentFile={setCurrentFile}
/>
{!isOnlyView ? (
<div className="space-y-12">
<div className="space-y-2 pb-6">
<Label>Or upload an agreement</Label>
<div className="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<DocumentUpload
currentFile={currentFile}
setCurrentFile={setCurrentFile}
/>
</div>
</div>
</div>
</div>
) : null}
</div>
</div>

<SheetFooter className="flex-shrink-0">
<SheetFooter
className={`flex-shrink-0 ${isOnlyView ? "mt-6" : ""}`}
>
<div className="flex items-center">
<Button type="submit" loading={isLoading}>
Create Agreement
</Button>
{isOnlyView ? (
<Button type="button" onClick={() => handleClose(false)}>
Close
</Button>
) : (
<Button type="submit" loading={isLoading}>
Create Agreement
</Button>
)}
</div>
</SheetFooter>
</form>
Expand Down
9 changes: 6 additions & 3 deletions components/links/link-sheet/agreement-section.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useMemo } from "react";

import { motion } from "motion/react";

Expand All @@ -17,6 +17,7 @@ import { DEFAULT_LINK_TYPE } from ".";
import AgreementSheet from "./agreement-panel";
import LinkItem from "./link-item";
import { LinkUpgradeOptions } from "./link-options";
import { Agreement } from "@prisma/client";

export default function AgreementSection({
data,
Expand All @@ -39,6 +40,8 @@ export default function AgreementSection({
const [isAgreementSheetVisible, setIsAgreementSheetVisible] =
useState<boolean>(false);

const filteredAgreements = useMemo(() => agreements.filter((agreement: Agreement) => !agreement.deletedAt || agreement.id === agreementId), [agreements]);

useEffect(() => {
setEnabled(enableAgreement!);
}, [enableAgreement]);
Expand Down Expand Up @@ -98,8 +101,8 @@ export default function AgreementSection({
<SelectValue placeholder="Select an agreement" />
</SelectTrigger>
<SelectContent>
{agreements &&
agreements.map(({ id, name }) => (
{filteredAgreements &&
filteredAgreements.map(({ id, name }) => (
<SelectItem key={id} value={id}>
{name}
</SelectItem>
Expand Down
18 changes: 15 additions & 3 deletions components/links/link-sheet/allow-list-section.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect, useState } from "react";

import { LinkPreset } from "@prisma/client";
import { motion } from "motion/react";

import { Textarea } from "@/components/ui/textarea";

import { FADE_IN_ANIMATION_SETTINGS } from "@/lib/constants";
import { sanitizeAllowDenyList } from "@/lib/utils";

import { Textarea } from "@/components/ui/textarea";

import { DEFAULT_LINK_TYPE } from ".";
import LinkItem from "./link-item";
import { LinkUpgradeOptions } from "./link-options";
Expand All @@ -16,6 +17,7 @@ export default function AllowListSection({
setData,
isAllowed,
handleUpgradeStateChange,
presets,
}: {
data: DEFAULT_LINK_TYPE;
setData: React.Dispatch<React.SetStateAction<DEFAULT_LINK_TYPE>>;
Expand All @@ -25,6 +27,7 @@ export default function AllowListSection({
trigger,
plan,
}: LinkUpgradeOptions) => void;
presets: LinkPreset | null;
}) {
const { emailProtected, allowList } = data;

Expand All @@ -46,6 +49,13 @@ export default function AllowListSection({
}));
}, [allowListInput, emailProtected, enabled, setData]);

useEffect(() => {
if (isAllowed && presets?.allowList && presets.allowList.length > 0) {
setEnabled(true);
setAllowListInput(presets.allowList.join("\n") || "");
}
}, [presets, isAllowed]);

const handleEnableAllowList = () => {
const updatedEnabled = !enabled;
setEnabled(updatedEnabled);
Expand Down Expand Up @@ -99,7 +109,9 @@ export default function AllowListSection({
<Textarea
className="focus:ring-inset"
rows={5}
placeholder="Enter allowed emails/domains, one per line, e.g. [email protected] @example.org"
placeholder={`Enter allowed emails/domains, one per line, e.g.
[email protected]
@example.org`}
value={allowListInput}
onChange={handleAllowListChange}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback, memo } from "react";

import { ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { Trash2 } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand All @@ -26,7 +26,7 @@ interface CustomFieldProps {
isLast?: boolean;
}

export default function CustomField({
export default memo(function CustomField({
field,
onUpdate,
onDelete,
Expand All @@ -39,7 +39,7 @@ export default function CustomField({

useEffect(() => {
onUpdate(localField);
}, [localField, onUpdate]);
}, [localField]);

const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement>,
Expand Down Expand Up @@ -166,4 +166,4 @@ export default function CustomField({
</div>
</div>
);
}
});
24 changes: 11 additions & 13 deletions components/links/link-sheet/custom-fields-panel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CustomField, CustomFieldType } from "@prisma/client";
import { Plus } from "lucide-react";
import { toast } from "sonner";
import { useCallback, useMemo } from "react";

import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
Expand Down Expand Up @@ -40,14 +41,13 @@ export default function CustomFieldsPanel({
}) {
const { isDatarooms, isDataroomsPlus, isBusiness } = usePlan();

const getFieldLimit = () => {
const fieldLimit = useMemo(() => {
if (isDatarooms || isDataroomsPlus) return 3;
if (isBusiness) return 1;
return 0;
};
}, [isDatarooms, isDataroomsPlus, isBusiness]);

const addField = () => {
const fieldLimit = getFieldLimit();
const addField = useCallback(() => {
if (fields.length >= fieldLimit) {
toast.error(
`You can only add up to ${fieldLimit} custom field${fieldLimit === 1 ? "" : "s"} on the ${isDatarooms ? "Data Rooms" : "Business"} plan`,
Expand All @@ -65,24 +65,24 @@ export default function CustomFieldsPanel({
orderIndex: fields.length,
};
onChange([...fields, newField]);
};
}, [fields, fieldLimit, isDatarooms, onChange]);

const updateField = (index: number, updatedField: CustomFieldData) => {
const updateField = useCallback((index: number, updatedField: CustomFieldData) => {
const newFields = [...fields];
newFields[index] = updatedField;
onChange(newFields);
};
}, [fields, onChange]);

const removeField = (index: number) => {
const removeField = useCallback((index: number) => {
const newFields = fields.filter((_, i) => i !== index);
// Update orderIndex for remaining fields
newFields.forEach((field, i) => {
field.orderIndex = i;
});
onChange(newFields);
};
}, [fields, onChange]);

const moveField = (index: number, direction: "up" | "down") => {
const moveField = useCallback((index: number, direction: "up" | "down") => {
if (
(direction === "up" && index === 0) ||
(direction === "down" && index === fields.length - 1)
Expand All @@ -102,9 +102,7 @@ export default function CustomFieldsPanel({
});

onChange(newFields);
};

const fieldLimit = getFieldLimit();
}, [fields, onChange]);

return (
<Sheet open={isConfigOpen} onOpenChange={setIsConfigOpen}>
Expand Down
Loading