Implemented create protfolio api

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-12 11:53:03 +01:00
parent 1e305912f6
commit 0ce99e0f94
6 changed files with 155 additions and 34 deletions

View file

@ -1,6 +1,55 @@
import { eq } from "drizzle-orm";
import { portfolio, portfolioUsers } from "@/app/db/schema/portfolio";
import {
portfolio,
portfolioUsers,
PortfolioGoal,
PortfolioStatus,
PortfolioRole,
} from "@/app/db/schema/portfolio";
import type { NewPortfolio } from "@/app/db/schema/portfolio";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/app/db/db";
import { z } from "zod";
export async function POST(request: NextRequest) {}
const createPortfolioSchema = z.object({
userId: z.number(),
portfolioName: z.string(),
budget: z.number().optional(),
goal: z.enum(PortfolioGoal),
status: z.enum(PortfolioStatus),
role: z.enum(PortfolioRole),
});
export async function POST(request: NextRequest) {
const body = await request.json();
try {
const validatedBody = createPortfolioSchema.parse(body);
const { userId, portfolioName, budget, goal, status, role } = validatedBody;
const creationDate = new Date();
const newPortfolio = await db
.insert(portfolio)
.values({
name: portfolioName,
budget: budget,
goal: goal,
status: status,
createdAt: creationDate,
updatedAt: creationDate,
})
.returning({ portfolioId: portfolio.id });
const newPortfolioId = newPortfolio[0].portfolioId;
const response = await db
.insert(portfolioUsers)
.values({ userId: userId, portfolioId: newPortfolioId, role: role })
.returning();
return new NextResponse(JSON.stringify(response), { status: 201 });
} catch (error) {
console.error(error);
throw error;
}
}

View file

@ -1,35 +1,102 @@
import { useMutation } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import {
PortfolioStatus,
PortfolioGoal,
PortfolioRole,
} from "@/app/db/schema/portfolio";
type CreatePortfolioArgs = {
userId: number;
portfolioName: string;
budget: number | undefined;
goal: (typeof PortfolioGoal)[number];
status: (typeof PortfolioStatus)[number];
role: (typeof PortfolioRole)[number];
};
const createPortfolio = async ({
userId,
portfolioName,
budget,
goal,
status,
role,
}: CreatePortfolioArgs) => {
const requestBody = JSON.stringify({
userId: userId,
portfolioName: portfolioName,
budget: budget,
goal: goal,
status: status,
role: role,
});
const response = await fetch("/api/portfolio/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: requestBody,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
};
const ModalSubmit = ({
buttonDisabled,
porfolioName,
portfolioName,
budget,
objective,
}: {
buttonDisabled: boolean;
porfolioName: string;
budget: string;
portfolioName: string;
budget: number | undefined;
objective: string;
}) => {
const router = useRouter();
function createPortfolio() {
// TODO: This will be a server action that will create a new portfolio in the database
// That endpoint will return a uuid which will be the new portfolio's id
const id = "f290f1ee-6c54-4b01-90e6-d701748f0851";
router.push(
`/portfolio/${id}?name=${porfolioName}&budget=${budget}&objective=${objective}`
);
}
const { mutate, isLoading } = useMutation(createPortfolio, {
onSuccess: (data) => {
// handle success, e.g. navigate to new page
console.log("SUCCESS", data);
router.push(
`/portfolio/${data.id}?name=${portfolioName}&budget=${budget}&objective=${objective}`
);
},
onError: (error) => {
// handle error
console.log(error);
},
});
const handleSubmit = () => {
const userId = 2;
const status = "scoping";
const role = "creator";
mutate({
userId,
portfolioName: portfolioName,
budget,
goal: objective,
status,
role,
});
};
return (
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-brandtan px-4 py-2 text-sm font-medium text-grey-900 hover:bg-hovertan 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={createPortfolio}
disabled={buttonDisabled}
onClick={handleSubmit}
disabled={buttonDisabled || isLoading}
>
Create
{isLoading ? "Creating..." : "Create"}
</button>
);
};

View file

@ -6,14 +6,7 @@ import CarbonIcon from "./CarbonIcon";
import QuestionMarkIcon from "./QuestionMarkIcon";
import EnvironmentIcon from "./EnvironmentIconSvg";
import ModalSubmit from "./ModalSubmit";
const outcomes = [
"Valuation Improvement",
"Increasing EPC",
"Reducing CO2 emissions",
"Energy Savings",
"Nothing Specific",
];
import { PortfolioGoal } from "@/app/db/schema/portfolio";
// Mock Icon component
interface IconProps {
@ -39,7 +32,7 @@ const Icon: React.FC<IconProps> = ({ name, selected, onSelect }: IconProps) => {
// add more mappings here if needed
"Increasing EPC": <EnvironmentIcon fill="white" />,
"Reducing CO2 emissions": <CarbonIcon fill="white" />,
"Nothing Specific": <QuestionMarkIcon fill="white" />,
None: <QuestionMarkIcon fill="white" />,
};
return (
@ -66,7 +59,7 @@ export default function NewPortfolioModal({
setIsOpen: (isOpen: boolean) => void;
}) {
const [portfolioName, setPortfolioName] = useState("");
const [budget, setBudget] = useState("");
const [budget, setBudget] = useState<undefined | number>(undefined);
const [selectedOutcome, setSelectedOutcome] =
useState<string>("Nothing Specific");
const [buttonDisabled, setButtonDisabled] = useState(true);
@ -153,7 +146,7 @@ export default function NewPortfolioModal({
type="number"
placeholder="1000"
value={budget}
onChange={(e) => setBudget(e.target.value)}
onChange={(e) => setBudget(Number(e.target.value))}
onKeyDown={(e) =>
(e.key === "e" || e.key === "E") &&
e.preventDefault()
@ -170,7 +163,7 @@ export default function NewPortfolioModal({
Select Outcome:
</span>
<div className="flex space-x-2">
{outcomes.map((outcome) => (
{PortfolioGoal.map((outcome) => (
<Icon
key={outcome}
name={outcome}
@ -192,7 +185,7 @@ export default function NewPortfolioModal({
</button>
<ModalSubmit
buttonDisabled={buttonDisabled}
porfolioName={portfolioName}
portfolioName={portfolioName}
budget={budget}
objective={selectedOutcome}
/>

View file

@ -1,5 +1,5 @@
import CardTiles from "../components/home/CardTiles";
import getPortfolios from "./utils";
import { getPortfolios } from "./utils";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/route";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";

View file

@ -6,9 +6,7 @@ import { NextRequest, NextResponse } from "next/server";
import { portfolio } from "@/app/db/schema/portfolio";
import type { Portfolio } from "@/app/db/schema/portfolio";
export default async function getPortfolios(
userId: number
): Promise<Portfolio[]> {
export async function getPortfolios(userId: number): Promise<Portfolio[]> {
const userPortfolios = await db
.select()
.from(portfolio)

14
src/types/next-auth.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
import NextAuth from "next-auth";
// This extends the session object to allow us to insert user data into it
declare module "next-auth" {
interface Session {
user: {
id: string;
} & DefaultSession["user"];
}
interface User {
dbId: number;
}
}