diff --git a/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx
new file mode 100644
index 00000000..38ea37a3
--- /dev/null
+++ b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx
@@ -0,0 +1,480 @@
+"use client";
+
+import { Dialog, Transition, Menu } from "@headlessui/react";
+import { useState, Fragment, useEffect, useMemo } from "react";
+import { Input } from "@/app/shadcn_components/ui/input";
+import { Button } from "@/app/shadcn_components/ui/button";
+import { Float } from "@headlessui-float/react";
+import { ChevronDownIcon } from "@heroicons/react/20/solid";
+import { useMutation } from "@tanstack/react-query";
+import { useSession } from "next-auth/react";
+
+type Option = {
+ label: string;
+ value: string;
+ disabled: boolean;
+};
+
+type DropdownProps = {
+ options: Option[];
+ selectedOption: string;
+ onSelectOption: (option: Option) => void;
+};
+
+const selecthousingTypeOptions = [
+ {
+ label: "Social",
+ value: "Social",
+ disabled: false,
+ },
+ {
+ label: "Private",
+ value: "Private",
+ disabled: false,
+ },
+];
+
+const selectGoalOptions = [
+ {
+ label: "Increase EPC",
+ value: "Increase EPC",
+ disabled: false,
+ },
+ {
+ label: "Reduce energy consumption",
+ value: "Reduce energy consumption",
+ disabled: true,
+ },
+];
+
+const goalValueOptions = [
+ {
+ label: "C",
+ value: "C",
+ disabled: false,
+ },
+ {
+ label: "B",
+ value: "B",
+ disabled: false,
+ },
+ {
+ label: "A",
+ value: "A",
+ disabled: false,
+ },
+];
+
+export function SelectDropdown({
+ options,
+ selectedOption,
+ onSelectOption,
+}: DropdownProps) {
+ return (
+
+ );
+}
+
+async function uploadCsvToS3({
+ presignedUrl,
+ file,
+}: {
+ presignedUrl: string;
+ file: Blob;
+}) {
+ try {
+ const response = await fetch(presignedUrl, {
+ method: "PUT",
+ body: file,
+ headers: { "Content-Type": "text/csv" },
+ });
+
+ if (!response.ok) {
+ console.error(response);
+ throw new Error("Network response was not ok");
+ }
+ } catch (error) {
+ console.error(error);
+ throw new Error("Upload failed.");
+ }
+
+ return { success: true };
+}
+
+async function generatePresignedUrl({
+ userId,
+ portfolioId,
+ fileKey,
+}: {
+ userId: string;
+ portfolioId: string;
+ fileKey: string;
+}) {
+ // fileKey is a location in S3 where we want to upload the file
+ const response = await fetch("/api/upload/csv", {
+ method: "POST",
+ body: JSON.stringify({
+ userId,
+ portfolioId,
+ fileKey,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to generate presigned url");
+ }
+
+ return response.json();
+}
+
+function generateS3Keys(userId: string, portfolioId: string) {
+ const timestamp = new Date().toISOString().replace(/[:.-]/g, "");
+ const assetListFileKey = `${userId}/${portfolioId}/${timestamp}/asset_list.csv`;
+ const valuationDataFileKey = `${userId}/${portfolioId}/${timestamp}/valuation_data.csv`;
+ return { assetListFileKey, valuationDataFileKey };
+}
+
+type GenericObject = Record;
+
+const convertToCSV = (data: T[]): string => {
+ // Get headers (keys from the first object)
+ const headers = Object.keys(data[0]) as (keyof T)[];
+
+ // Create CSV rows
+ const rows = data.map((row) =>
+ headers.map((header) => row[header]).join(",")
+ );
+
+ // Combine headers and rows into CSV string
+ return [headers.join(","), ...rows].join("\n");
+};
+
+function useCreateRemoteAssessment({
+ portfolioId,
+ uprn,
+ addressLineOne,
+ postcode,
+}: {
+ portfolioId: string;
+ uprn: number | null;
+ addressLineOne: string;
+ postcode: string;
+}) {
+ // 1) We want to upload the asset data. To do this, we format the asset data, generate a presigned URL, and upload the data to S3.
+ // 2) We then want to upload valuation data. To do this, we format the valuation data, generate a presigned URL, and upload the data to S3.
+ // 3) Trigger the engine!!!! This is an api at /api/plan/trigger with our body that we looked at in Miro
+
+ // Set up the mutation with react-query, to generate a presigned URL
+
+ const session = useSession();
+ const userId = String(session.data?.user.dbId);
+
+ const { assetListFileKey, valuationDataFileKey } = useMemo(
+ () => generateS3Keys(userId, portfolioId),
+ [userId, portfolioId]
+ );
+
+ const {
+ mutate: mutateUploadAssetList,
+ isLoading: uploadAssetListIsLoading,
+ isError: uploadAssetListIsError,
+ } = useMutation(uploadCsvToS3, {
+ onSuccess: (data) => {
+ console.log("WAS IT A SUCCESS?", data.success);
+ console.log("TRIGGERING THE ENGINE");
+ // This is where we trigger the engine!!!
+ const body = {
+ trigger_file_path: assetListFileKey,
+ };
+ // engine API call goes here
+ },
+ onError: (error) => {
+ console.error(error);
+ },
+ });
+
+ const {
+ mutate: mutatePresignedUrl,
+ isLoading: presignedUrlIsLoading,
+ isError: presignedUrlIsError,
+ } = useMutation(generatePresignedUrl, {
+ onSuccess: (data) => {
+ console.log(data.url);
+ // On success, upload to that URL!!!!
+ const assetList = [
+ {
+ uprn: uprn,
+ address: addressLineOne,
+ postcode: postcode,
+ },
+ ];
+ const assetListCsvString = convertToCSV(assetList);
+ const assetListCsv = new Blob([assetListCsvString], {
+ type: "text/csv",
+ });
+
+ mutateUploadAssetList({ presignedUrl: data.url, file: assetListCsv });
+ },
+ onError: (error) => {
+ console.error(error);
+ },
+ });
+
+ function handleSubmit() {
+ mutatePresignedUrl({ userId, portfolioId, fileKey: assetListFileKey });
+ console.log("SUCCESS"); // This is where we would want to trigger some kind of use feedback
+ }
+
+ return {
+ handleSubmit,
+ presignedUrlIsLoading,
+ presignedUrlIsError,
+ };
+}
+
+export default function RemoteAssessmentModal({
+ portfolioId,
+ isOpen,
+ setIsOpen,
+}: {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ portfolioId: string;
+}) {
+ const [scenario, setScenario] = useState(undefined);
+ const [housingType, sethousingType] = useState("");
+ const [selectedGoal, setSelectedGoal] = useState("");
+ const [goalValue, setGoalValue] = useState("");
+ const [addressLineOne, setAddressLineOne] = useState("");
+ const [postcode, setPostcode] = useState("");
+ const [uprn, setUprn] = useState(null);
+ const [valuation, setValuation] = useState("");
+ const [buttonDisabled, setButtonDisabled] = useState(true);
+
+ function handleScenarioChange(event: React.ChangeEvent) {
+ setScenario(event.target.value);
+ }
+
+ function handleAddressLineOneChange(
+ event: React.ChangeEvent
+ ) {
+ setAddressLineOne(event.target.value);
+ }
+
+ function handlePostcodeChange(event: React.ChangeEvent) {
+ setPostcode(event.target.value);
+ }
+
+ function handleUprnChange(event: React.ChangeEvent) {
+ setUprn(Number(event.target.value));
+ }
+
+ function handleValuationChange(event: React.ChangeEvent) {
+ setValuation(event.target.value);
+ }
+
+ const { handleSubmit, presignedUrlIsLoading, presignedUrlIsError } =
+ useCreateRemoteAssessment({
+ portfolioId,
+ uprn,
+ addressLineOne,
+ postcode,
+ });
+
+ useEffect(() => {
+ function handleButtonDisabled(): boolean {
+ return !(
+ scenario &&
+ selectedGoal &&
+ housingType &&
+ addressLineOne &&
+ postcode &&
+ uprn &&
+ valuation
+ );
+ }
+
+ setButtonDisabled(handleButtonDisabled());
+ }, [
+ scenario,
+ selectedGoal,
+ housingType,
+ addressLineOne,
+ postcode,
+ uprn,
+ valuation,
+ ]);
+
+ return (
+ <>
+
+
+
+ >
+ );
+}