Skip to content

Individual Contract

FullContract Component Documentation

Overview

The FullContract component in the Rifbid application displays detailed information about a specific contract and provides various functionalities, including adding the contract to a user’s list, applying for the contract, and requesting proposal assistance.

Key Features

  1. Contract Details: Displays detailed information about the contract, including title, description, agency, point of cotact, location, posted date, and due date.
  2. My Contracts: Allows users to add or remove contracts from their “My Contracts” list.
  3. Apply Button: Provides an option to apply for the contract and tracks engagement metrics.
  4. Proposal Assistance: Offers assistance for creating proposals via a partnered consultancy.
  5. Similar Contracts: Displays a list of similar contracts based on the current contract’s details.
  6. Rating the Contract: Provides users the opportunity to rate the relevance of the contract.

Dependencies

  • React: For building the component.
  • Firebase: For Firestore database interactions.
  • axios: For making HTTP requests.
  • Custom UI Components: Card, Button, Badge, etc.

State Management

The component uses useState and useEffect hooks for managing state and side effects.

const [contract, setContract] = useState<ContractProps | null>(null);
const [similarContracts, setSimilarContracts] = useState<ContractProps[]>([]);
const [showFullDescription, setShowFullDescription] = useState(false);
const [activeContracts, setActiveContracts] = useState([]);
const [triggerReender, setTriggerRerender] = useState(false);
const [supplierProfile, setSupplierProfile] =
useState<SupplierUserProfile | null>(null);
const [token, setToken] = useState<string | undefined>("");

Fetching Contract Details

Fetches the contract details and similar contracts based on the contract ID.

useEffect(() => {
const fetchContract = async () => {
setIsLoading(true);
const token = await getToken();
try {
const response = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/tenders/${id}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
const contract = response.data;
setContract(contract);
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
const checkIfContractExistsInOrganization = async () => {
const token = await getToken();
if (token) {
const response = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/suppliers/check-organization-contract/${id}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
// Assuming the backend returns a `exists` property in the response
setContractExistsInOrganization(response.data.exists);
setAddedBy(response.data.added_by);
}
};
const fetchSimilarContracts = async () => {
const token = await getToken();
if (contract) {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/tenders/similar-tenders`,
{ embedding: contract.summary_embedding },
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
const similarContracts = response.data.tenders;
setSimilarContracts(similarContracts);
}
};
if (!contract) {
fetchContract();
checkIfContractExistsInOrganization();
}
if (similarContracts.length === 0) fetchSimilarContracts();
}, [id, contract, getToken, supplierProfile]);

Adding to My Contracts

Adds the contract to the user’s list of contracts.

const handleAdd = async () => {
setContractActionLoading(true);
// Check if user is authenticated
if (supplierProfile) {
try {
// Assuming 'contract' is the object retrieved and stored in state from your API
if (contract) {
const transformedData: Organization_Contract = {
title: contract.title, // Keep 'title' as 'title'
description: contract.description, // Map 'full_description' to 'description'
industry: contract.industry, // Map 'category' to 'industry'
contract_id: contract.serial_id,
organization_id: supplierProfile.organization_id,
added_by: supplierProfile.user_id,
assigned_to: [],
//@ts-ignore
expiry_date: new Date(contract.submission_deadline.$date),
};
// handleAddContract(transformedData);
const token = await getToken();
if (token) {
const responseObj = await addContractToOrganizationContracts(
transformedData,
token,
);
if (responseObj?.status === "success") {
toast("Contract added to my contracts list");
setContractExistsInOrganization(true);
setAddedBy(supplierProfile.user_id);
} else {
if (responseObj.message) toast(responseObj.message);
}
}
}
} catch (error) {
console.error("Error adding document: ", error);
} finally {
setContractActionLoading(false);
}
} else {
console.error("User not authenticated");
}
};

Rendering the Component

Displays contract details, similar contracts, and provides actions like applying, adding to list, and requesting assistance.

return (
<>
{currentUser?.emailVerified ? (
<div className="bg-gray-800 p-1 text-white md:col-span-2 md:p-4">
<Link href={`/find-contracts?${queryUrlString}`}>
{" "}
<Button variant={"dark"}>Go Back</Button>
</Link>
<Card className="bg-gray-200 my-5 grid grid-cols-6 gap-4 text-black md:p-4">
<div className="col-span-6 lg:col-span-4">
<CardHeader>
<CardTitle className="text-xl font-bold">
<CardTitle className="text-base font-bold md:text-2xl">
{contract.title}
</CardTitle>
</CardTitle>
</CardHeader>
<div className="ps-6">
<Badge variant="secondary">{contract.industry}</Badge>
<div className="mt-2 text-sm md:text-xl">
<strong>Agency:</strong> {contract.agency}
</div>
<div className="mt-1 text-sm md:text-xl">
<strong>Location:</strong> {contract.work_location}
</div>
<div className="mt-1 text-sm md:text-xl">
<strong>Point of contact:</strong>{" "}
{contract.contact_information
? contract.contact_information
: "N/A"}
</div>
<div className="mt-1 text-sm md:text-xl">
<strong>Posted:</strong>{" "}
{contract.created_at.$date.substring(0, 10)}
</div>
<div className="mt-1 text-sm md:text-xl">
<strong>Due Date:</strong>{" "}
{contract.submission_deadline.$date.substring(0, 10)}
</div>
</div>
</div>
<div className="col-span-6 mr-4 mt-4 text-center lg:col-span-2 lg:mt-4">
<Button
onClick={handleApplyBtnClicked}
disabled={supplierCanApply}
className="w-[90%] md:w-full"
>
{contract.source === "gov" ? "Easy Apply" : "Apply Now"}
</Button>
<>
{!contractExistsInOrganization ? (
<Button
variant="outline"
className="mt-3 w-[90%] md:w-full"
onClick={handleAdd}
>
{contractActionLoading ? (
<Spinner className="text-black" />
) : (
"Add to My Contracts List"
)}
</Button>
) : (
<Button
variant="outline"
className="mt-3 w-[90%] md:w-full"
onClick={() => {
deleteItem(contract.serial_id);
}}
disabled={
supplierProfile?.role !== "admin" &&
supplierProfile?.user_id !== addedBy
}
>
{contractActionLoading ? (
<Spinner className="text-black" />
) : (
"Remove From My Contracts List"
)}
</Button>
)}
</>
<HelpWithContract contract={contract} />
<Link href={`/generate-proposal/${contract.serial_id}`}>
<Button variant="dark" className="mt-4 w-[90%] md:w-full">
Generate Proposal Using AI
</Button>
</Link>
</div>
<div className="col-span-6 mt-5">
<CardFooter>
<div>
<div>
<strong>Description:</strong>
<p className="mt-3">{stripHTML(contract.description)}</p>
</div>
{contract.gov_attachment &&
contract.gov_attachment.length > 0 && (
<div className="mt-5">
<strong>Attachments</strong>
<ul className="mt-2">
{contract.gov_attachment.map((attachment, i) => {
return (
<li key={i}>
<a
href={attachment}
className="text-green underline"
target="blank"
>
{" "}
{i === 0 ? "EOI.docx" : "TOR.docx"}
</a>
</li>
);
})}
</ul>
</div>
)}
{contract.attachments && contract.attachments.length > 0 && (
<div className="mt-5">
<strong>Attachments</strong>
<ul className="mt-2">
{contract.attachments.map((attachment, i) => {
return (
<li key={i}>
<a
href={attachment.url}
className="text-green underline"
target="blank"
>
{attachment.name}
</a>
</li>
);
})}
</ul>
</div>
)}
<div className="mt-4">
<strong>Rate this tender recommendation:</strong>
<span className="relative inline-block">
<CiCircleInfo
size={20}
className="ml-2 cursor-pointer"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
{showTooltip && (
<div
style={{
backgroundColor: "rgba(0, 0, 0, 0.5)",
color: "white",
padding: "8px 12px",
borderRadius: "4px",
fontSize: "14px",
position: "absolute",
zIndex: 10,
bottom: "100%",
left: "50%",
transform: "translateX(-5%)",
whiteSpace: "nowrap",
}}
>
We are collecting user feedback to ensure you get better tenders recommended to you.
</div>
)}
</span>
<div className="flex items-center mt-2">
{[...Array(5)].map((_, index) => {
const value = index + 1;
return (
<svg
key={value}
className="w-8 h-8 cursor-pointer"
fill={value <= (hover || rating) ? "#ecfa57" : "#d3d4d2"}
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={1}
onClick={() => handleClick(value)}
onMouseEnter={() => setHover(value)}
onMouseLeave={() => setHover(0)}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
/>
</svg>
);
})}
</div>
</div>
</div>
</CardFooter>
</div>
</Card>
<RecommendedCollaborators
embedding={contract.summary_embedding}
contract={contract}
/>
<div className="mt-10 px-5">
<h2 className="mb-5 text-center text-2xl font-bold text-black">
Similar Contracts
</h2>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{similarContracts.map((contract) => (
<SimilarContract contract={contract} key={contract._id.$oid} />
))}
</div>
</div>
</div>
) : (
<VerifyEmail />
)}
</>
);

HelpWithContract Component Documentation

Overview

The HelpWithContract component provides users with an option to request assistance with creating a proposal for a specific contract. It leverages a partnered consultancy service to offer this help.

Key Features

  1. Proposal Assistance Request: Allows users to request assistance with proposals.
  2. Dialog Interface: Uses a dialog interface for user interaction.
  3. Consultancy Requests Tracking: Updates the count of consultancy requests in the database.

Dependencies

  • React: For building the component.
  • axios: For making HTTP requests.
  • Firebase: For Firestore database interactions.
  • Custom UI Components: Dialog, Button, Textarea, etc.

State Management

The component uses useState for managing state.

const [assistanceRequested, setAssistanceRequested] = useState(false);
const [message, setMessage] = useState("");

Request Assistance Handler

Handles the submission of the assistance request.

const handleRequest = async () => {
try {
const responseObj = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/proposal-assistance`,
{ message, contract },
{ headers: { Authorization: "Bearer " + token } }
);
const response = responseObj.data;
if (response.message === "Request Sent") {
toast(
"An email has been sent to our partners, they will reach out to you within 48 hours"
);
router.push("/dashboard");
} else {
toast("An error has occurred, please try again later");
}
} catch (error) {
console.log(error);
}
};

Updating Consultancy Requests Count

Updates the count of consultancy requests in the database.

const updateAssistanceRequestCount = async () => {
const adminkpisDocRef = doc(db, "adminkpis", "U0G73Idf9J9wlIcPMnIY");
try {
await updateDoc(adminkpisDocRef, {
consultancy_requests: increment(1),
});
console.log("Increment successful");
} catch (error) {
console.error("Error incrementing consultancy_requests", error);
}
};

Rendering the Component

Displays the dialog interface for requesting assistance.

return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary" className="mt-3 w-full">
Need Help Winning The Contract?
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
{!assistanceRequested ? (
<>
<DialogHeader>
<DialogTitle>Proposal Assistance</DialogTitle>
<DialogDescription>
Rifbid has partnered with the best government contracting
consultancy agencies boasting over 98.5% proposal success rate.
Would you like to book a free consultation?
</DialogDescription>
</DialogHeader>
<div className="d-flex">
<Button
className="me-5"
type="button"
onClick={() => {
setAssistanceRequested(true);
updateAssistanceRequestCount();
}}
>
Yes
</Button>
<DialogClose asChild>
<Button type="button" variant="secondary">
No
</Button>
</DialogClose>
</div>
</>
) : (
<>
<DialogHeader>
<DialogTitle>Proposal Assistance</DialogTitle>
<DialogDescription>
Write a short description on what you would like assistance with
</DialogDescription>
</DialogHeader>
<div>
<Textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</div>
<Button onClick={handleRequest}>Submit</Button>
</>
)}
</DialogContent>
</Dialog>
);

Tender Ratings

Overview

The Tender Ratings feature in the FullContract component allows suppliers to provide feedback on recommended tenders by rating them on a scale of 1 to 5 stars. This feedback is essential for improving the recommendation algorithm to better match tenders with user preferences.


Key Features

  1. Interactive Rating System:

    • Users can rate tenders by clicking on stars, with a hover effect to preview their selection.
    • Ratings are collected to enhance future recommendations.
  2. Real-Time Feedback:

    • Submitted ratings are immediately sent to the backend for processing and storage.
  3. Data Submission:

    • Ratings are securely transmitted using an API endpoint, with user authentication ensured via tokens.
  4. Tooltips for User Guidance:

    • Provides additional context on the purpose of rating tenders through a tooltip.

Usage

The rating system is displayed as part of the FullContract component and appears below the tender description. It allows users to provide feedback on a specific tender.


Component Structure

  1. Stars Display:

    • The stars represent a 1-to-5 rating system.
    • Hover effects highlight the stars based on the user’s selection.
  2. Tooltip:

    • A tooltip appears when the user hovers over the info icon next to the rating prompt.
    • Provides context about how ratings are used to improve recommendations.
  3. Submit Rating:

    • When a user clicks a star, the selected rating is submitted to the backend via an API call.

Rating Submission Flow

  1. User Interaction:

    • Users click on a star to select their rating.
  2. API Request:

    • A POST request is sent to the /tenders/rate-tender endpoint with the following parameters:
      • user_id: ID of the logged-in user.
      • contract_id: Unique identifier for the tender.
      • rating: The selected rating value.
  3. Success/Failure Notification:

    • Displays a toast notification indicating whether the rating submission was successful.

Conclusion

This documentation provides an overview of the FullContract and HelpWithContract components, their key features, state management, and critical functions. For further details, refer to the components’ source code in the project directory.