Almost got upload flow working, need to address cors error

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-14 13:16:36 +01:00
parent 6624690f39
commit c8e200eda7
4 changed files with 95 additions and 34 deletions

View file

@ -13,7 +13,7 @@ function generateS3Key(userId: number, portfolioId: number, filename: string) {
return key;
}
export async function GET(request: NextRequest) {
export async function POST(request: NextRequest) {
// For the moment, this api specifically handles uploads of csvs
const body = await request.json();

View file

@ -55,7 +55,11 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
setIsUploadCsvOpen={setModalIsOpen}
/>
</NavigationMenuList>
<UploadCsvModal isOpen={modalIsOpen} setIsOpen={setModalIsOpen} />
<UploadCsvModal
isOpen={modalIsOpen}
setIsOpen={setModalIsOpen}
portfolioId={portfolioId}
/>
</NavigationMenu>
);
}

View file

@ -9,63 +9,119 @@ import { Input } from "@/app/shadcn_components/ui/input";
import { Label } from "@/app/shadcn_components/ui/label";
import { useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import Link from "next/link";
import { useSession } from "next-auth/react";
async function generatePresignedUrl({
userId,
portfolioId,
}: {
userId: number;
portfolioId: number;
}) {
const body = JSON.stringify({ userId: userId, portfolioId: portfolioId });
console.log("body before: ", body);
const presignedResponse = await fetch(`/api/upload/csv`, {
method: "POST",
body: body,
});
if (!presignedResponse.ok) {
throw new Error("Network response was not ok");
}
const presignedUrl = await presignedResponse.json();
return presignedUrl;
}
async function uploadCsvToS3({
presignedUrl,
file,
}: {
presignedUrl: string;
file: File;
}) {
console.log("Uploading file to s3");
const upload = await fetch(presignedUrl, {
method: "PUT",
body: file,
});
if (!upload.ok) {
throw new Error("Upload failed.");
}
return upload;
}
export const SubmitPlan = ({
buttonDisabled,
goal,
housingType,
goalValue,
file,
portfolioId,
}: {
buttonDisabled: boolean;
goal: string;
housingType: string;
goalValue: string;
file: File | null;
portfolioId: number;
}) => {
const router = useRouter();
const session = useSession();
// This state will hold the presignedUrl once it is available
const [presignedUrl, setPresignedUrl] = useState(null);
async function triggerPlanBuild() {
const requestBody = JSON.stringify({
goal: goal,
goalValue: goalValue,
housingType: housingType,
});
const response = await fetch("/api/portfolio/plan", {
method: "POST",
headers: {
"Content-Type": "application/json",
const { mutate: mutateUploadCsv, isLoading: isUploadLoading } = useMutation(
uploadCsvToS3,
{
onSuccess: (data) => {
return data;
},
onError: (error) => {
// handle error
console.error(error);
},
body: requestBody,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
);
return response.json();
}
const { mutate, isLoading } = useMutation(triggerPlanBuild, {
const { mutate, isLoading } = useMutation(generatePresignedUrl, {
onSuccess: (data) => {
console.log("uploading csv");
// router.push(`/portfolio/${data.id}`);
// After the presigned URL has been generated, we can upload the file to S3
mutateUploadCsv({ presignedUrl: data.url, file: file });
},
onError: (error) => {
// handle error
console.log(error);
console.error(error);
},
});
const handleSubmit = () => {
mutate();
if (!session.data) {
// The user is not logged in, redirect them to sign in
router.push("/");
return null;
}
const userId = session.data.user.dbId;
const handlePlanBuild = () => {
// The plan build is triggered by clicking submit which will:
// 1) Generate a pre-signed url to upload to
// 2) Upload the csv to the pre-signed url
// 3) Trigger the job to build the plan
// 4) Redirect the user to some loading page - this could be the portfolio page itself and we just trigger a regresh with skeleton cards for the properties
mutate({ userId: userId, portfolioId: portfolioId });
// mutateUploadCsv(presignedUrl);
// console.log("Redirect user to loading page");
// router.push("/portfolio/somewhere");
};
return (
<button
type="button"
className="text-white inline-flex justify-center rounded-md border border-transparent bg-brandblue px-4 py-2 text-sm font-medium text-grey-900 hover:bg-hoverblue focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:bg-gray-300 disabled:opacity-50"
onClick={handleSubmit}
onClick={handlePlanBuild}
disabled={buttonDisabled || isLoading}
>
{isLoading ? "Creating..." : "Create"}
@ -233,9 +289,11 @@ const goalValueOptions = [
export default function UploadCsvModal({
isOpen = false,
setIsOpen,
portfolioId,
}: {
isOpen?: boolean;
setIsOpen: (isOpen: boolean) => void;
portfolioId: number;
}) {
const [budget, setBudget] = useState<undefined | number>(undefined);
@ -246,10 +304,6 @@ export default function UploadCsvModal({
const [csvFile, setCsvFile] = useState<File | null>(null);
function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
setCsvFile(e.target.files ? e.target.files[0] : null);
}
function handleBudgeChange(e: React.ChangeEvent<HTMLInputElement>) {
setBudget(e.target.valueAsNumber);
}
@ -447,6 +501,8 @@ export default function UploadCsvModal({
goal={goalValue}
housingType={housingType}
goalValue={goalValue}
portfolioId={portfolioId}
file={csvFile}
/>
</div>
</Dialog.Panel>

View file

@ -260,7 +260,8 @@ export default async function Page({
// This is temp until we retrieve this data from the frontend
// TODO: Update the objects to contains objective + any other required information
const portfolioId = params.slug;
// We explcitly cast the slug to a number since it will be a string
const portfolioId = Number(params.slug);
const {
name: portfolioName,
budget,